合并最新master分支
合入 origin/master 最新画布、外部接口与后端模块化改动。 按新队列语义解决画布测试和原生壳检查冲突。 把编辑器 Agent 迁移到模块化实现并适配 LlmRunRequest。 删除已废弃的旧编辑器 Agent 单文件与个人 Claude 本地配置。
This commit is contained in:
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(npm test:*)",
|
||||
"Bash(netstat -ano)",
|
||||
"Bash(npm run:*)",
|
||||
"Bash(findstr :8081)",
|
||||
"Bash(taskkill:*)",
|
||||
"Bash(findstr LISTENING)",
|
||||
"Bash(npx tsc:*)",
|
||||
"Bash(lsof -ti:8081)",
|
||||
"Bash(curl -s http://localhost:8081/health)",
|
||||
"Bash(npm -v)",
|
||||
"Bash(cargo --version)",
|
||||
"Bash(rustc --version)",
|
||||
"Skill(code-review)",
|
||||
"Skill(code-review:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -318,6 +318,14 @@ For image edit/redraw that should replace an existing canvas layer, pass `projec
|
||||
|
||||
For sound effects and BGM, `assetFolderId` and `assetLabel` can write the generated audio to the account asset library, same as image/video generation.
|
||||
|
||||
## Successful Responses with Warnings
|
||||
|
||||
Character image generation (including character redraw through `kind: "character"`), icon spritesheet generation, and UI asset extraction can return HTTP 2xx with an optional structured `warning`. A 2xx response means the task completed, but it does not guarantee that every requested post-processed derivative exists.
|
||||
|
||||
- Apply the returned `project` and media snapshots before interpreting optional derivatives: character responses use `resource` / `asset`, while icon spritesheet and UI extraction responses use `spritesheetResource` / `spritesheetAsset`. When `warning.code` is `postprocess-failed-source-preserved`, the saved provider source image is the authoritative main result. Character output has no transparent derivative; icon spritesheet and UI extraction output have neither a transparent spritesheet nor slices. Display `warning.reason` directly, and do not synthesize missing derivatives or restart generation.
|
||||
- `sliceWarning` is a separate condition used only when transparent spritesheet post-processing succeeded but automatic slicing failed. Keep `sliceWarning.reason` as the original diagnostic and continue using the complete transparent spritesheet; a UI may add context when displaying it, but must not rewrite the stored reason.
|
||||
- The service contract keeps `warning` and `sliceWarning` mutually exclusive. As defensive handling for a malformed response containing both, treat the general `warning` as authoritative and do not misclassify the source-preserved result as a slicing-only warning.
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Do not invent endpoints outside the OpenAPI, especially internal worker or runtime task-list routes.
|
||||
|
||||
@@ -78,6 +78,14 @@ Ask a follow-up only when two routes could both be correct and produce different
|
||||
|
||||
All generation requests should be placed into both the current canvas and its same-name asset-library folder. For endpoints that support `assetLabel`, pass it. For UI extraction, use `spritesheetLabel`. For icon spritesheet, the folder is enough. For character animation, the endpoint does not return `asset`; after success call `POST /api/external/v1/editor/assets` using the first returned frame as `imageSrc`, the session `assetFolderId`, and `assetKind: "character-animation"`.
|
||||
|
||||
## HTTP 2xx Warning Handling
|
||||
|
||||
Character image generation (including character redraw through `kind: "character"`), icon spritesheet generation, and UI asset extraction may return HTTP 2xx while carrying a structured `warning`; completion does not imply that all post-processed derivatives exist.
|
||||
|
||||
- Consume the returned `project` and media snapshots as authoritative: character responses use `resource` / `asset`, while icon spritesheet and UI extraction responses use `spritesheetResource` / `spritesheetAsset`. `warning.code: "postprocess-failed-source-preserved"` means the saved provider source is the main result. Character output has no transparent derivative, while icon spritesheet and UI extraction have no transparent spritesheet and no slices. Display `warning.reason` directly; do not construct missing assets or retry the provider generation from scratch.
|
||||
- `sliceWarning` is only for a transparent spritesheet that was created successfully but could not be split automatically. Use the complete transparent spritesheet and preserve `sliceWarning.reason` as the original diagnostic; it is not a post-processing/source-preserved warning.
|
||||
- The service contract keeps `warning` and `sliceWarning` mutually exclusive. If a malformed response contains both, prioritize the general `warning` over `sliceWarning` defensively.
|
||||
|
||||
## Reference Image Upload
|
||||
|
||||
If the user provides a local file as a reference image, run upload before the generation request:
|
||||
|
||||
@@ -78,7 +78,7 @@ Best practices:
|
||||
- Avoid overlapping queries that duplicate row delivery.
|
||||
- Use indexes for subscribed filters.
|
||||
|
||||
## 2.2.0 to 2.6.0 Delta
|
||||
## 2.2.0 to 2.6.1 Delta
|
||||
|
||||
Genarrative introduced SpacetimeDB around 2.2.0. Important changes since then:
|
||||
|
||||
@@ -88,6 +88,7 @@ Genarrative introduced SpacetimeDB around 2.2.0. Important changes since then:
|
||||
- **2.4.1**: Rust and TypeScript procedural views can declare primary keys, enabling `OnUpdate` events for subscribed views; fixed index schema from ST tables.
|
||||
- **2.5.0**: procedures are stable, C# procedural views gain primary keys, event tables allow broader layout-altering automigrations, BTreeSet storage makes row insertion deterministic and avoids accidentally quadratic bulk insert behavior, `wasm_memory_bytes` billing metric semantics changed, template version constraints unified, `publish --delete-data` config fallback fixed, CLI `call` accepts hex Identity arguments.
|
||||
- **2.6.0**: procedural-view primary keys are available across Rust, TypeScript, and C#, commitlog gains `max_segment_size` / `write_buffer_size` / `preallocate_segments`, the default write buffer increases for throughput, event-table automigrations improve, and CLI binary distribution expands.
|
||||
- **2.6.1**: procedure contexts again receive the caller `Identity` and `ConnectionId`; generated TypeScript `Option<T>` fields use optional keys; `spacetime init --template` lists available templates when no template argument is supplied.
|
||||
|
||||
## Debugging Checklist
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@ GENARRATIVE_LLM_PROVIDER="ark"
|
||||
GENARRATIVE_LLM_BASE_URL="https://ark.cn-beijing.volces.com/api/v3"
|
||||
GENARRATIVE_LLM_API_KEY="eb750614-e0b5-402a-bfea-4224862d251e"
|
||||
GENARRATIVE_LLM_MODEL="doubao-1-5-pro-32k-character-250715"
|
||||
GENARRATIVE_EDITOR_BGFILTER_BASE_URL="https://u1082648-b442-cd409e05.westx.seetacloud.com:8443"
|
||||
APIMART_BASE_URL="https://api.apimart.ai/v1"
|
||||
APIMART_API_KEY=""
|
||||
APIMART_IMAGE_REQUEST_TIMEOUT_MS=180000
|
||||
@@ -36,6 +37,7 @@ DASHSCOPE_SCENE_IMAGE_MODEL="wan2.2-t2i-flash"
|
||||
DASHSCOPE_REFERENCE_IMAGE_MODEL="qwen-image-2.0"
|
||||
DASHSCOPE_COVER_IMAGE_MODEL="wan2.2-t2i-flash"
|
||||
ARK_CHARACTER_VIDEO_REQUEST_TIMEOUT_MS=420000
|
||||
|
||||
# 启用服务端大模型调试日志(记录所有输入输出)
|
||||
LLM_DEBUG_LOG="true"
|
||||
|
||||
|
||||
@@ -24,6 +24,40 @@ module.exports = {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/scripts/**/*.{ts,js,mjs,cjs}'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['**/*.test.{ts,tsx,js,mjs,cjs}'],
|
||||
rules: {
|
||||
'no-console': 'off',
|
||||
'unused-imports/no-unused-vars': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['miniprogram/**/*.js'],
|
||||
globals: {
|
||||
App: 'readonly',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: [
|
||||
'apps/admin-web/src/pages/*.tsx',
|
||||
'src/components/platform-entry/PlatformMobileHomeWelcomeDialog.tsx',
|
||||
],
|
||||
rules: {
|
||||
'react-refresh/only-export-components': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/components/platform-entry/PlatformEntryFlowShellImpl.tsx'],
|
||||
rules: {
|
||||
'react-hooks/exhaustive-deps': 'off',
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['src/components/game-canvas/**/*.tsx'],
|
||||
rules: {
|
||||
@@ -61,7 +95,25 @@ module.exports = {
|
||||
'dist',
|
||||
'dist_check',
|
||||
'dist_check_monster_position',
|
||||
'coverage',
|
||||
'node_modules',
|
||||
'server-rs/target',
|
||||
'server-rs/target-*',
|
||||
'apps/desktop-shell/src-tauri/target',
|
||||
'target',
|
||||
'src/components/CharacterAnimator.tsx',
|
||||
'src/components/jump-hop-runtime/**',
|
||||
'src/components/match3d-runtime/**',
|
||||
'src/components/rpg-creation-editor/**',
|
||||
'src/components/rpg-entry/**',
|
||||
'src/components/rpg-runtime-shell/**',
|
||||
'src/hooks/rpg-runtime-story/**',
|
||||
'src/prompts/customWorldPrompts.ts',
|
||||
'src/services/ai.ts',
|
||||
'src/services/miniGameDraftGenerationProgress.ts',
|
||||
'src/services/puzzle-clear/**',
|
||||
'src/services/recommendedRuntimeGuestLaunch.test.ts',
|
||||
'src/data/sceneEncounterPreviews.ts',
|
||||
'public/Icons',
|
||||
'media',
|
||||
'.codex-logs',
|
||||
|
||||
+2
-1
@@ -48,6 +48,7 @@ temp*build*/
|
||||
/.app/
|
||||
/target/
|
||||
/logs
|
||||
/.claude/settings.local.json
|
||||
/.codegraph/
|
||||
/.playwright-cli/
|
||||
**/.playwright-cli/
|
||||
@@ -57,7 +58,7 @@ temp*build*/
|
||||
.worktrees/
|
||||
.rag/
|
||||
.env.secrets.local
|
||||
.claude/settings.local.json
|
||||
nohup.out
|
||||
spacetime.local.json
|
||||
deploy/container/api-server.env
|
||||
deploy/container/worker-smoke/
|
||||
|
||||
@@ -98,21 +98,20 @@ npm run check:content
|
||||
主运行时:
|
||||
|
||||
- [src/App.tsx](./src/App.tsx)
|
||||
- [src/components/GameShell.tsx](./src/components/GameShell.tsx)
|
||||
- [src/AuthenticatedApp.tsx](./src/AuthenticatedApp.tsx)
|
||||
- [src/routing/appRoutes.tsx](./src/routing/appRoutes.tsx)
|
||||
- [src/hooks/useCombatFlow.ts](./src/hooks/useCombatFlow.ts)
|
||||
- [src/hooks/useStoryGeneration.ts](./src/hooks/useStoryGeneration.ts)
|
||||
|
||||
主流程内嵌编辑能力:
|
||||
|
||||
- [src/components/CustomWorldEntityEditorModal.tsx](./src/components/CustomWorldEntityEditorModal.tsx)
|
||||
- [src/components/CustomWorldNpcVisualEditor.tsx](./src/components/CustomWorldNpcVisualEditor.tsx)
|
||||
- [src/components/CustomWorldRoleAssetStudioModal.tsx](./src/components/CustomWorldRoleAssetStudioModal.tsx)
|
||||
- [src/components/image-editor/ImageCanvasEditorView.tsx](./src/components/image-editor/ImageCanvasEditorView.tsx)
|
||||
- [src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx](./src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx)
|
||||
- [src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.tsx](./src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.tsx)
|
||||
|
||||
核心数据:
|
||||
|
||||
- [src/data/scenePresets.ts](./src/data/scenePresets.ts)
|
||||
- [src/data/characterPresets.ts](./src/data/characterPresets.ts)
|
||||
- [src/data/monsterPresets.ts](./src/data/monsterPresets.ts)
|
||||
- [src/data/npcInteractions.ts](./src/data/npcInteractions.ts)
|
||||
- [src/data/treasureInteractions.ts](./src/data/treasureInteractions.ts)
|
||||
|
||||
|
||||
@@ -2,23 +2,21 @@ import type {
|
||||
AdminAccountListResponse,
|
||||
AdminCreateAccountRequest,
|
||||
AdminCreateAccountResponse,
|
||||
AdminUpsertCreationEntryEventBannersRequest,
|
||||
AdminUpsertCreationEntryTypeConfigRequest,
|
||||
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
||||
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
|
||||
AdminCreationEntryConfigResponse,
|
||||
AdminDashboardQuery,
|
||||
AdminDashboardResponse,
|
||||
AdminDebugHttpRequest,
|
||||
AdminDebugHttpResponse,
|
||||
AdminDisableProfileRedeemCodeRequest,
|
||||
AdminDisableProfileTaskConfigRequest,
|
||||
AdminDatabaseTableListResponse,
|
||||
AdminDatabaseTableRowsQuery,
|
||||
AdminDatabaseTableRowsResponse,
|
||||
AdminCreateEditorShowcaseCampaignImageUploadTicketRequest,
|
||||
AdminCreateEditorShowcaseCampaignImageUploadTicketResponse,
|
||||
AdminDebugHttpRequest,
|
||||
AdminDebugHttpResponse,
|
||||
AdminDirectUploadTicketPayload,
|
||||
AdminDisableProfileRedeemCodeRequest,
|
||||
AdminDisableProfileTaskConfigRequest,
|
||||
AdminEditorAssetListQuery,
|
||||
AdminEditorAssetListResponse,
|
||||
AdminDirectUploadTicketPayload,
|
||||
AdminEditorShowcaseAssetResponse,
|
||||
AdminEditorShowcaseCampaignResponse,
|
||||
AdminEditorShowcaseDisplayRequest,
|
||||
@@ -37,14 +35,16 @@ import type {
|
||||
AdminRechargeRefundPreviewRequest,
|
||||
AdminRechargeRefundPreviewResponse,
|
||||
AdminRechargeRefundRegisterRequest,
|
||||
AdminTrackingEventListQuery,
|
||||
AdminTrackingEventKeyListResponse,
|
||||
AdminTrackingEventListQuery,
|
||||
AdminTrackingEventListResponse,
|
||||
AdminUpdateWorkVisibilityRequest,
|
||||
AdminUpdateWorkVisibilityResponse,
|
||||
AdminUpdateAccountRequest,
|
||||
AdminUpdateAccountResponse,
|
||||
AdminUpdateWorkVisibilityRequest,
|
||||
AdminUpdateWorkVisibilityResponse,
|
||||
AdminUploadedEditorShowcaseCampaignImage,
|
||||
AdminUpsertCreationEntryEventBannersRequest,
|
||||
AdminUpsertCreationEntryTypeConfigRequest,
|
||||
AdminUpsertEditorShowcaseCampaignRequest,
|
||||
AdminUpsertFeatureGateConfigRequest,
|
||||
AdminUpsertProfileInviteCodeRequest,
|
||||
@@ -53,11 +53,11 @@ import type {
|
||||
AdminUpsertProfileTaskConfigRequest,
|
||||
AdminUpsertProfileWalletConfigRequest,
|
||||
AdminUpsertPublicWorkInteractionConfigRequest,
|
||||
AdminWorkVisibilityListResponse,
|
||||
AdminUserDetailQuery,
|
||||
AdminUserDetailResponse,
|
||||
AdminWalletRestrictionRequest,
|
||||
AdminWalletRestrictionResponse,
|
||||
AdminWorkVisibilityListResponse,
|
||||
ApiErrorEnvelope,
|
||||
ApiMeta,
|
||||
ApiSuccessEnvelope,
|
||||
|
||||
@@ -17,26 +17,25 @@ import {
|
||||
getStoredAdminToken,
|
||||
setStoredAdminToken,
|
||||
} from '../auth/adminAuthStore';
|
||||
import {AdminCreationEntrySwitchPage} from '../pages/AdminCreationEntrySwitchPage';
|
||||
import {AdminAccountsPage} from '../pages/AdminAccountsPage';
|
||||
import {AdminCreationEntrySwitchPage} from '../pages/AdminCreationEntrySwitchPage';
|
||||
import {AdminDashboardPage} from '../pages/AdminDashboardPage';
|
||||
import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage';
|
||||
import {AdminDatabaseTablesPage} from '../pages/AdminDatabaseTablesPage';
|
||||
import {AdminInviteCodePage} from '../pages/AdminInviteCodePage';
|
||||
import {AdminLoginPage} from '../pages/AdminLoginPage';
|
||||
import {AdminEditorGenerationPricingPage} from '../pages/AdminEditorGenerationPricingPage';
|
||||
import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage';
|
||||
import {AdminEditorAssetQueryPage} from '../pages/AdminEditorAssetQueryPage';
|
||||
import {AdminEditorGenerationPricingPage} from '../pages/AdminEditorGenerationPricingPage';
|
||||
import {AdminEditorShowcaseReviewPage} from '../pages/AdminEditorShowcaseReviewPage';
|
||||
import {AdminGrayReleaseConfigPage} from '../pages/AdminGrayReleaseConfigPage';
|
||||
import {AdminInviteCodePage} from '../pages/AdminInviteCodePage';
|
||||
import {AdminLoginPage} from '../pages/AdminLoginPage';
|
||||
import {AdminOverviewPage} from '../pages/AdminOverviewPage';
|
||||
import {AdminProfileWalletConfigPage} from '../pages/AdminProfileWalletConfigPage';
|
||||
import {AdminRechargeProductPage} from '../pages/AdminRechargeProductPage';
|
||||
import {AdminRechargeOrderPage} from '../pages/AdminRechargeOrderPage';
|
||||
import {AdminRechargeProductPage} from '../pages/AdminRechargeProductPage';
|
||||
import {AdminRedeemCodePage} from '../pages/AdminRedeemCodePage';
|
||||
import {AdminTaskConfigPage} from '../pages/AdminTaskConfigPage';
|
||||
import {AdminTrackingEventsPage} from '../pages/AdminTrackingEventsPage';
|
||||
import {AdminWorkVisibilityPage} from '../pages/AdminWorkVisibilityPage';
|
||||
import {AdminShell} from './AdminShell';
|
||||
import type {AdminRouteId} from './adminRoutes';
|
||||
import {
|
||||
getAccessibleAdminRoutes,
|
||||
@@ -44,6 +43,7 @@ import {
|
||||
resolveAdminRoute,
|
||||
routeHash,
|
||||
} from './adminRoutes';
|
||||
import {AdminShell} from './AdminShell';
|
||||
|
||||
type SessionStatus = 'checking' | 'guest' | 'authenticated';
|
||||
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
import {
|
||||
Activity,
|
||||
Bug,
|
||||
BadgeDollarSign,
|
||||
Bug,
|
||||
Coins,
|
||||
LayoutDashboard,
|
||||
LogOut,
|
||||
Megaphone,
|
||||
Database,
|
||||
Eye,
|
||||
GitBranch,
|
||||
Images,
|
||||
Star,
|
||||
WalletCards,
|
||||
ShieldCheck,
|
||||
LayoutDashboard,
|
||||
ListChecks,
|
||||
LogOut,
|
||||
Megaphone,
|
||||
ReceiptText,
|
||||
ShieldCheck,
|
||||
SlidersHorizontal,
|
||||
Database,
|
||||
Star,
|
||||
Table2,
|
||||
TicketCheck,
|
||||
TicketPercent,
|
||||
ReceiptText,
|
||||
Users,
|
||||
WalletCards,
|
||||
} from 'lucide-react';
|
||||
import type {ReactNode} from 'react';
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import {
|
||||
adminTrackingEventDefinitions,
|
||||
buildAdminTrackingEventKeyOptions,
|
||||
filterAdminProfileTaskTrackingEventDefinitions,
|
||||
filterAdminTrackingEventKeyOptions,
|
||||
filterAdminTrackingEventDefinitions,
|
||||
filterAdminTrackingEventKeyOptions,
|
||||
findAdminTrackingEventDefinition,
|
||||
} from './trackingEventDefinitions';
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
/* @vitest-environment jsdom */
|
||||
|
||||
import {
|
||||
act,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
@@ -308,6 +309,9 @@ test('Dashboard 四张趋势图共享横向日期窗口', async () => {
|
||||
if (!sourceChart || !targetChart) {
|
||||
throw new Error('趋势图未完整渲染');
|
||||
}
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
setScrollableDimensions(sourceChart, {
|
||||
clientWidth: 100,
|
||||
scrollWidth: 500,
|
||||
@@ -319,11 +323,11 @@ test('Dashboard 四张趋势图共享横向日期窗口', async () => {
|
||||
scrollLeft: 0,
|
||||
});
|
||||
|
||||
fireEvent.scroll(sourceChart);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(targetChart.scrollLeft).toBe(200);
|
||||
await act(async () => {
|
||||
fireEvent.scroll(sourceChart);
|
||||
});
|
||||
|
||||
expect(targetChart.scrollLeft).toBe(200);
|
||||
});
|
||||
|
||||
test('Dashboard 访问人数趋势明确区分每日桶与时段去重总数', async () => {
|
||||
|
||||
@@ -2,12 +2,12 @@ import { ChevronDown, ChevronRight, Eye, FileText, RefreshCcw, X } from 'lucide-
|
||||
import { Fragment, type ReactNode } from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { AdminAssetReadUrlResponse } from '../api/adminApiClient';
|
||||
import {
|
||||
getAdminAssetReadUrl,
|
||||
isAdminApiError,
|
||||
listAdminEditorAssets,
|
||||
} from '../api/adminApiClient';
|
||||
import type { AdminAssetReadUrlResponse } from '../api/adminApiClient';
|
||||
import type {
|
||||
AdminEditorAssetListQuery,
|
||||
AdminEditorAssetPayload,
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Eye, FileText, RefreshCcw, Upload, X } from 'lucide-react';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import type { AdminAssetReadUrlResponse } from '../api/adminApiClient';
|
||||
import {
|
||||
getAdminAssetReadUrl,
|
||||
getAdminEditorShowcaseCampaign,
|
||||
@@ -11,7 +12,6 @@ import {
|
||||
uploadAdminEditorShowcaseCampaignImage,
|
||||
upsertAdminEditorShowcaseCampaign,
|
||||
} from '../api/adminApiClient';
|
||||
import type { AdminAssetReadUrlResponse } from '../api/adminApiClient';
|
||||
import type {
|
||||
AdminEditorShowcaseAssetPayload,
|
||||
AdminEditorShowcaseCampaignPayload,
|
||||
|
||||
@@ -10,13 +10,13 @@ import type {
|
||||
AdminTrackingEventKeyPayload,
|
||||
TrackingScopeKind,
|
||||
} from '../api/adminApiTypes';
|
||||
import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton';
|
||||
import {
|
||||
buildAdminTrackingEventKeyOptions,
|
||||
filterAdminTrackingEventKeyOptions,
|
||||
filterAdminTrackingEventDefinitions,
|
||||
filterAdminTrackingEventKeyOptions,
|
||||
findAdminTrackingEventDefinition,
|
||||
} from '../config/trackingEventDefinitions';
|
||||
import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton';
|
||||
import { handlePageError } from './pageUtils';
|
||||
|
||||
interface AdminTrackingEventsPageProps {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import fs from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const configPath = new URL('../src-tauri/tauri.conf.json', import.meta.url);
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
@@ -1117,7 +1117,7 @@ function extractDesktopCapabilities(source) {
|
||||
}
|
||||
|
||||
function extractDesktopHandledMethods(source) {
|
||||
const matchBodies = [...source.matchAll(/match request\.method\.as_str\(\) \{([\s\S]*?)\n \}/g)].map(
|
||||
const matchBodies = [...source.matchAll(/match request\.method\.as_str\(\) \{([\s\S]*?)\n {4}\}/g)].map(
|
||||
(match) => match[1],
|
||||
);
|
||||
if (matchBodies.length === 0) {
|
||||
@@ -1152,7 +1152,7 @@ function extractDesktopHostBridgeMethodBody(source, method) {
|
||||
|
||||
const nextMethodMatch = source
|
||||
.slice(methodStart + method.length)
|
||||
.match(/\n (?:"[^"]+"|_)\s*=>/);
|
||||
.match(/\n {8}(?:"[^"]+"|_)\s*=>/);
|
||||
const nextMethodStart = nextMethodMatch
|
||||
? methodStart + method.length + nextMethodMatch.index
|
||||
: -1;
|
||||
@@ -1410,10 +1410,6 @@ function extractTauriInvokeCommands(source) {
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
function collectRustSourceBasenames(files) {
|
||||
return files.map((file) => file.pathname.split('/').pop()).sort();
|
||||
}
|
||||
|
||||
function collectRustSourceRelativePaths(files) {
|
||||
const rootPath = rustSourceDir.pathname.endsWith('/')
|
||||
? rustSourceDir.pathname
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {spawnSync} from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {spawnSync} from 'node:child_process';
|
||||
|
||||
const shellRoot = new URL('../', import.meta.url);
|
||||
const repoRoot = path.resolve(shellRoot.pathname, '../..');
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import fs from 'node:fs';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
|
||||
import { PNG } from 'pngjs';
|
||||
|
||||
@@ -82,11 +82,6 @@ const filesPath = new URL('../src/host-bridge/files.ts', import.meta.url);
|
||||
const filesSource = fs.readFileSync(filesPath, 'utf8');
|
||||
const filesTestPath = new URL('../src/host-bridge/files.test.ts', import.meta.url);
|
||||
const filesTestSource = fs.readFileSync(filesTestPath, 'utf8');
|
||||
const filePayloadsPath = new URL(
|
||||
'../src/host-bridge/filePayloads.ts',
|
||||
import.meta.url,
|
||||
);
|
||||
const filePayloadsSource = fs.readFileSync(filePayloadsPath, 'utf8');
|
||||
const hapticsPath = new URL('../src/host-bridge/haptics.ts', import.meta.url);
|
||||
const hapticsSource = fs.readFileSync(hapticsPath, 'utf8');
|
||||
const hapticsTestPath = new URL(
|
||||
@@ -495,7 +490,7 @@ function extractNumberConstExport(source, exportName) {
|
||||
|
||||
function extractMobileBridgeHandledMethods(source) {
|
||||
const match = source.match(
|
||||
/async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n \}/,
|
||||
/async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n {2}\}/,
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error('unable to read mobile shell HostBridge handler methods');
|
||||
@@ -506,7 +501,7 @@ function extractMobileBridgeHandledMethods(source) {
|
||||
|
||||
function extractMobileBridgeUnsupportedMethods(source) {
|
||||
const match = source.match(
|
||||
/async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n \}/,
|
||||
/async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n {2}\}/,
|
||||
);
|
||||
if (!match) {
|
||||
throw new Error('unable to read mobile shell HostBridge unsupported methods');
|
||||
@@ -514,7 +509,7 @@ function extractMobileBridgeUnsupportedMethods(source) {
|
||||
|
||||
const unsupportedMethods = new Set();
|
||||
const casePattern =
|
||||
/case '([^']+)':([\s\S]*?)(?=\n case '|\n default:|\n \})/g;
|
||||
/case '([^']+)':([\s\S]*?)(?=\n {4}case '|\n {4}default:|\n {2}\})/g;
|
||||
for (const entry of match[1].matchAll(casePattern)) {
|
||||
if (entry[2].includes('unsupported(request.method)')) {
|
||||
unsupportedMethods.add(entry[1]);
|
||||
@@ -990,10 +985,6 @@ const sharedEvents = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EVENTS',
|
||||
);
|
||||
const sharedBlockedDownloadProtocols = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS',
|
||||
);
|
||||
const sharedMobileBaseCapabilities = extractStringArrayExport(
|
||||
sharedContractSource,
|
||||
'HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import fs from 'node:fs';
|
||||
import {spawnSync} from 'node:child_process';
|
||||
import fs from 'node:fs';
|
||||
|
||||
const shellRoot = new URL('../', import.meta.url);
|
||||
const easConfigPath = new URL('eas.json', shellRoot);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {spawnSync} from 'node:child_process';
|
||||
|
||||
import fs from 'node:fs';
|
||||
|
||||
const appConfigPath = new URL('../app.json', import.meta.url);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import {spawnSync} from 'node:child_process';
|
||||
|
||||
import fs from 'node:fs';
|
||||
|
||||
const shellRoot = new URL('../', import.meta.url);
|
||||
|
||||
@@ -625,24 +625,32 @@ describe('handleMobileHostBridgeMessage', () => {
|
||||
});
|
||||
|
||||
test('app.openExternalUrl 原生异常返回稳定 host_error', async () => {
|
||||
vi.mocked(Linking.canOpenURL).mockRejectedValueOnce({
|
||||
code: 'native_linking_failure',
|
||||
message: 'native linking failed',
|
||||
nativeStackIOS: ['private native frame'],
|
||||
});
|
||||
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined);
|
||||
try {
|
||||
vi.mocked(Linking.canOpenURL).mockRejectedValueOnce({
|
||||
code: 'native_linking_failure',
|
||||
message: 'native linking failed',
|
||||
nativeStackIOS: ['private native frame'],
|
||||
});
|
||||
|
||||
const response = await send(
|
||||
request('app.openExternalUrl', {
|
||||
url: 'https://example.com/path',
|
||||
}),
|
||||
);
|
||||
const response = await send(
|
||||
request('app.openExternalUrl', {
|
||||
url: 'https://example.com/path',
|
||||
}),
|
||||
);
|
||||
|
||||
const failedResponse = expectFailed(response);
|
||||
const failedResponse = expectFailed(response);
|
||||
|
||||
expect(failedResponse.error).toEqual({
|
||||
code: 'host_error',
|
||||
message: 'external URL cannot be opened',
|
||||
});
|
||||
expect(failedResponse.error).toEqual({
|
||||
code: 'host_error',
|
||||
message: 'external URL cannot be opened',
|
||||
});
|
||||
expect(warnSpy).toHaveBeenCalledWith(
|
||||
'mobile HostBridge navigation failed for external.open',
|
||||
);
|
||||
} finally {
|
||||
warnSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('app.openExternalUrl 拒绝危险协议', async () => {
|
||||
|
||||
@@ -1,4 +1,10 @@
|
||||
import { type HostBridgeRequest } from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
import { getMobileHostBridgeAppearanceColorScheme } from './appearance';
|
||||
import { setMobileAppBadgeCount } from './badge';
|
||||
import {
|
||||
readMobileHostBridgeClipboardText,
|
||||
writeMobileHostBridgeClipboardText,
|
||||
} from './clipboard';
|
||||
import {
|
||||
captureMobileHostBridgeImageFile,
|
||||
exportMobileHostBridgeAudioFile,
|
||||
@@ -9,32 +15,26 @@ import {
|
||||
importMobileHostBridgeImageFile,
|
||||
importMobileHostBridgeTextFile,
|
||||
} from './files';
|
||||
import {
|
||||
failure,
|
||||
type MobileHostBridgeNavigation,
|
||||
unsupported,
|
||||
} from './protocol';
|
||||
import { resetQrScannerForTest, scanMobileHostBridgeQrCode } from './scanner';
|
||||
import {
|
||||
openShare,
|
||||
resetMobileHostBridgeShareTargetForTest,
|
||||
setMobileHostBridgeShareTarget,
|
||||
} from './share';
|
||||
import { showMobileHostBridgeLocalNotification } from './notifications';
|
||||
import {
|
||||
readMobileHostBridgeClipboardText,
|
||||
writeMobileHostBridgeClipboardText,
|
||||
} from './clipboard';
|
||||
import { runMobileHostBridgeHapticsImpact } from './haptics';
|
||||
import { setMobileAppBadgeCount } from './badge';
|
||||
import { getMobileHostBridgeAppearanceColorScheme } from './appearance';
|
||||
import {
|
||||
openMobileHostBridgeExternalUrl,
|
||||
openMobileHostBridgeNativePage,
|
||||
reloadMobileHostBridgeWebView,
|
||||
} from './navigation';
|
||||
import { getMobileHostBridgeNetworkStatus } from './network';
|
||||
import { showMobileHostBridgeLocalNotification } from './notifications';
|
||||
import {
|
||||
failure,
|
||||
type MobileHostBridgeNavigation,
|
||||
unsupported,
|
||||
} from './protocol';
|
||||
import { getMobileHostBridgeRuntimeResponse } from './runtime';
|
||||
import { resetQrScannerForTest, scanMobileHostBridgeQrCode } from './scanner';
|
||||
import {
|
||||
openShare,
|
||||
resetMobileHostBridgeShareTargetForTest,
|
||||
setMobileHostBridgeShareTarget,
|
||||
} from './share';
|
||||
|
||||
let navigation: MobileHostBridgeNavigation | null = null;
|
||||
|
||||
|
||||
@@ -2,15 +2,15 @@ import type * as ImagePicker from 'expo-image-picker';
|
||||
|
||||
import {
|
||||
type FileImportImageResult,
|
||||
type HostBridgeAudioMimeType,
|
||||
type HostBridgeDocumentMimeType,
|
||||
type HostBridgeImageMimeType,
|
||||
type HostBridgeTextMimeType,
|
||||
HOST_BRIDGE_AUDIO_MIME_TYPES,
|
||||
HOST_BRIDGE_DOCUMENT_MIME_TYPES,
|
||||
HOST_BRIDGE_IMAGE_MIME_TYPES,
|
||||
HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES,
|
||||
HOST_BRIDGE_TEXT_MIME_TYPES,
|
||||
type HostBridgeAudioMimeType,
|
||||
type HostBridgeDocumentMimeType,
|
||||
type HostBridgeImageMimeType,
|
||||
type HostBridgeTextMimeType,
|
||||
normalizeHostBridgeExportFileName,
|
||||
normalizeHostBridgeImportFileName,
|
||||
} from '../../../../packages/shared/src/contracts/hostBridge';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user