@khorsheed/dsh-file-preview 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +11 -0
- package/LICENSE +21 -0
- package/README.en.md +88 -0
- package/README.i18n.yaml +6 -0
- package/README.md +88 -0
- package/cordis.patch.yml +7 -0
- package/lib/index.js +1338 -0
- package/lib/invariant.js +25 -0
- package/lib/tsconfig.tsbuildinfo +1 -0
- package/lib/typert.host.d.ts +3 -0
- package/lib/typert.host.js +549 -0
- package/lib/typert.remote-client.d.ts +35 -0
- package/lib/typert.remote-client.d.ts.map +1 -0
- package/lib/typert.remote-client.js +210 -0
- package/lib/types/bash-writes.d.ts +83 -0
- package/lib/types/bash-writes.d.ts.map +1 -0
- package/lib/types/bash-writes.js +367 -0
- package/lib/types/fold.d.ts +77 -0
- package/lib/types/fold.d.ts.map +1 -0
- package/lib/types/fold.js +309 -0
- package/lib/types/index.d.ts +105 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +517 -0
- package/lib/types/invariant.d.ts +16 -0
- package/lib/types/invariant.d.ts.map +1 -0
- package/lib/types/invariant.js +23 -0
- package/lib/types/reveal.d.ts +36 -0
- package/lib/types/reveal.d.ts.map +1 -0
- package/lib/types/reveal.js +77 -0
- package/lib/types/types.d.ts +124 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +2 -0
- package/package.json +81 -0
- package/skills/3d-artifact/SKILL.md +52 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cross-platform native "show in folder, selecting the file" dispatch used by
|
|
3
|
+
* the file-preview Remote's `reveal` method.
|
|
4
|
+
*
|
|
5
|
+
* The official host opener only opens a path with the default application —
|
|
6
|
+
* it has no select-in-folder intent — so this plugin carries its own
|
|
7
|
+
* shell-free dispatch: macOS `open -R` (Finder reveals and selects), Windows
|
|
8
|
+
* `explorer /select,<path>` (Explorer reveals and selects), WSL translates the
|
|
9
|
+
* path to the Windows spelling first, and desktop Linux tries the
|
|
10
|
+
* select-capable file managers (`nautilus` / `dolphin` / `nemo` `--select`) in
|
|
11
|
+
* order, falling back to the caller's parent-folder open when none is
|
|
12
|
+
* installed. Every command runs through the official
|
|
13
|
+
* `@deepseek-ai/dsh-native-command` runner — no shell, abort propagation.
|
|
14
|
+
* @module @khorsheed/dsh-file-preview
|
|
15
|
+
*/
|
|
16
|
+
import { release as osRelease } from 'node:os';
|
|
17
|
+
import { runNativeCommand } from '@deepseek-ai/dsh-native-command';
|
|
18
|
+
/** Whether one environment marker is set to a non-empty value. */
|
|
19
|
+
function present(value) {
|
|
20
|
+
return value !== undefined && value !== '';
|
|
21
|
+
}
|
|
22
|
+
/** Distinguish WSL from desktop Linux using its process and kernel markers. */
|
|
23
|
+
function isWsl(internals) {
|
|
24
|
+
const env = internals.env ?? process.env;
|
|
25
|
+
if (present(env.WSL_DISTRO_NAME) || present(env.WSL_INTEROP))
|
|
26
|
+
return true;
|
|
27
|
+
return (internals.osRelease ?? osRelease()).toLowerCase().includes('microsoft');
|
|
28
|
+
}
|
|
29
|
+
/** Desktop file managers with a documented `--select <path>` flag, tried in order. */
|
|
30
|
+
const SELECT_FILE_MANAGERS = ['nautilus', 'dolphin', 'nemo'];
|
|
31
|
+
/**
|
|
32
|
+
* Reveal one path in the host file manager, opening its folder and selecting
|
|
33
|
+
* the path. Throws when the platform cannot select (no select-capable file
|
|
34
|
+
* manager, unsupported platform) — the caller then opens the parent folder.
|
|
35
|
+
* @param path - absolute path in the host's execution world (caller owns
|
|
36
|
+
* resolution, typically `FileSystem.processPath`).
|
|
37
|
+
* @param signal - caller/connection lifetime; abort terminates the native command.
|
|
38
|
+
* @param internals - platform and environment seam for deterministic tests.
|
|
39
|
+
*/
|
|
40
|
+
export async function revealNativePath(path, signal, internals = {}) {
|
|
41
|
+
const platform = internals.platform ?? process.platform;
|
|
42
|
+
const run = internals.run ?? runNativeCommand;
|
|
43
|
+
if (platform === 'linux' && isWsl(internals)) {
|
|
44
|
+
const translated = await run('wslpath', ['-w', path], signal);
|
|
45
|
+
signal.throwIfAborted();
|
|
46
|
+
const windowsPath = translated.stdout.replace(/[\r\n]+$/, '');
|
|
47
|
+
if (windowsPath === '')
|
|
48
|
+
throw new Error('wslpath returned no Windows path');
|
|
49
|
+
await run('explorer.exe', [`/select,${windowsPath}`], signal);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (platform === 'darwin') {
|
|
53
|
+
await run('open', ['-R', path], signal);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
if (platform === 'win32') {
|
|
57
|
+
await run('explorer.exe', [`/select,${path}`], signal);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (platform === 'linux') {
|
|
61
|
+
let lastError;
|
|
62
|
+
for (const manager of SELECT_FILE_MANAGERS) {
|
|
63
|
+
try {
|
|
64
|
+
await run(manager, ['--select', path], signal);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
catch (error) {
|
|
68
|
+
// A missing manager (ENOENT) or a failed launch: try the next one.
|
|
69
|
+
lastError = error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
throw lastError instanceof Error
|
|
73
|
+
? lastError
|
|
74
|
+
: new Error('no select-capable file manager found');
|
|
75
|
+
}
|
|
76
|
+
throw new Error(`native path reveal is unsupported on ${platform}`);
|
|
77
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/** Wire payload vocabulary of the file-preview Remote service. @module @khorsheed/dsh-file-preview/types */
|
|
2
|
+
/** The model-facing operation recorded on a file. */
|
|
3
|
+
export type FilePreviewOp = 'read' | 'write' | 'edit';
|
|
4
|
+
/** One file the session read, wrote, or edited, with the location of its last occurrence. */
|
|
5
|
+
export interface FilePreviewEntry {
|
|
6
|
+
/** Display path exactly as the tool call recorded it (relative to the session cwd when it was). */
|
|
7
|
+
readonly path: string;
|
|
8
|
+
/** The last recorded operation. */
|
|
9
|
+
readonly op: FilePreviewOp;
|
|
10
|
+
/** Seq of the last `tool/call` event recorded for this path. */
|
|
11
|
+
readonly seq: number;
|
|
12
|
+
/** Turn of the last occurrence. */
|
|
13
|
+
readonly turn: number;
|
|
14
|
+
/** Step of the last occurrence. */
|
|
15
|
+
readonly step: number;
|
|
16
|
+
/** Each write/edit change's diff, captured from the tool results' presentation meta, in event order. */
|
|
17
|
+
readonly diffs: readonly FilePreviewDiff[];
|
|
18
|
+
/** The last write/edit change's diff (the final `diffs` entry, for callers that want just the latest). */
|
|
19
|
+
readonly lastDiff?: {
|
|
20
|
+
readonly oldText: string | null;
|
|
21
|
+
readonly newText: string;
|
|
22
|
+
} | undefined;
|
|
23
|
+
}
|
|
24
|
+
/** One write/edit change of a file, in event order, with where it happened. */
|
|
25
|
+
export interface FilePreviewDiff {
|
|
26
|
+
/** Seq of the `tool/result` event whose meta carried this diff. */
|
|
27
|
+
readonly seq: number;
|
|
28
|
+
/** Turn of the change. */
|
|
29
|
+
readonly turn: number;
|
|
30
|
+
/** Step of the change. */
|
|
31
|
+
readonly step: number;
|
|
32
|
+
/** Prior content, or null for a new file / an overwrite. */
|
|
33
|
+
readonly oldText: string | null;
|
|
34
|
+
/** Content after the change. */
|
|
35
|
+
readonly newText: string;
|
|
36
|
+
}
|
|
37
|
+
/** Whole-list response of `filePreview.list`. */
|
|
38
|
+
export interface FilePreviewList {
|
|
39
|
+
/** Files in first-seen order, deduplicated by display path. */
|
|
40
|
+
readonly entries: readonly FilePreviewEntry[];
|
|
41
|
+
/** Seq of the last scanned session event (`-1` for an empty log). */
|
|
42
|
+
readonly asOfSeq: number;
|
|
43
|
+
/** Whether the list was capped at the service's `maxFiles` limit. */
|
|
44
|
+
readonly truncated: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Read outcome classification of `filePreview.read`. */
|
|
47
|
+
export type FilePreviewReadKind = 'text' | 'image' | 'binary' | 'missing' | 'too-large' | 'error';
|
|
48
|
+
/** One `filePreview.read` response; exactly one of the content arms is populated. */
|
|
49
|
+
export interface FilePreviewRead {
|
|
50
|
+
/** The resolved display path of the read target. */
|
|
51
|
+
readonly path: string;
|
|
52
|
+
/** Outcome classification; `text`/`image` carry a renderable value, `error` carries message. */
|
|
53
|
+
readonly kind: FilePreviewReadKind;
|
|
54
|
+
/** Current text content for `text` reads, capped by the service's `maxReadBytes`
|
|
55
|
+
* (HTML files by `htmlMaxReadBytes`). */
|
|
56
|
+
readonly content?: string;
|
|
57
|
+
/** Browser-loadable URL for `image` reads (host-served bytes, session-scoped). */
|
|
58
|
+
readonly url?: string;
|
|
59
|
+
/** Whether `content` was truncated to the byte cap. */
|
|
60
|
+
readonly truncated?: boolean;
|
|
61
|
+
/** HTML files only: best-effort hint that the document contains scripts
|
|
62
|
+
* (`<script>` tags, inline event handlers, or `javascript:` URLs). A hint
|
|
63
|
+
* for default-mode selection and warning — never a trust decision; the
|
|
64
|
+
* sandbox and CSP are the real boundary. Absent for non-HTML reads and for
|
|
65
|
+
* HTML without script markers. */
|
|
66
|
+
readonly htmlScripted?: boolean;
|
|
67
|
+
/** Byte size of the file when the backend reported one. */
|
|
68
|
+
readonly size?: number;
|
|
69
|
+
/** Human-readable failure detail for `error` reads. */
|
|
70
|
+
readonly message?: string;
|
|
71
|
+
}
|
|
72
|
+
/** Service configuration, validated by schemastery and documented in the config catalog. */
|
|
73
|
+
export interface FilePreviewConfig {
|
|
74
|
+
/** Byte cap for a single `read`; larger files answer `too-large` without reading. */
|
|
75
|
+
readonly maxReadBytes?: number;
|
|
76
|
+
/** Byte cap for reading HTML/HTM files — the sandboxed render channel can
|
|
77
|
+
* handle far larger documents than the code view, so HTML gets its own,
|
|
78
|
+
* wider cap (default 4 MiB) while every other text read keeps
|
|
79
|
+
* `maxReadBytes`. */
|
|
80
|
+
readonly htmlMaxReadBytes?: number;
|
|
81
|
+
/** Cap on the number of entries `list` returns. */
|
|
82
|
+
readonly maxFiles?: number;
|
|
83
|
+
/** Whether the bash-write collector watches sessions for files bash wrote
|
|
84
|
+
* (heredocs, `>` redirects, `tee`, `sed -i`) and adds them to `list`. On by
|
|
85
|
+
* default; disable to keep the list log-derived only. */
|
|
86
|
+
readonly captureBashWrites?: boolean;
|
|
87
|
+
}
|
|
88
|
+
/** Outcome of a `filePreview.reveal` call (the "show in folder" gesture). */
|
|
89
|
+
export type FilePreviewReveal = {
|
|
90
|
+
/** The host file manager opened the file's folder with the file selected. */
|
|
91
|
+
readonly revealed: true;
|
|
92
|
+
} | {
|
|
93
|
+
/** The file could not be revealed; the caller opens the parent folder instead. */
|
|
94
|
+
readonly revealed: false;
|
|
95
|
+
/** Why: `missing` = the recorded path does not resolve to an existing target; `select-failed` = the host could not select the file. */
|
|
96
|
+
readonly reason: 'missing' | 'select-failed';
|
|
97
|
+
};
|
|
98
|
+
/** One file's mutation facts within one turn — the turn card's vocabulary. */
|
|
99
|
+
export interface FilePreviewTurnFile {
|
|
100
|
+
/** The display path (absolute for bash-captured writes; as-recorded otherwise). */
|
|
101
|
+
readonly path: string;
|
|
102
|
+
/** Seq of the first mutation recorded for this path in this turn (stable ordering). */
|
|
103
|
+
readonly seq: number;
|
|
104
|
+
readonly step: number;
|
|
105
|
+
/** Lines added across the turn's mutations of this path. */
|
|
106
|
+
readonly added?: number;
|
|
107
|
+
/** Lines removed; 0 for a create; absent when an uncounted mutation (a write
|
|
108
|
+
* whose prior content was not diffable, a Code Mode dispatch) reported no
|
|
109
|
+
* total. */
|
|
110
|
+
readonly removed?: number;
|
|
111
|
+
}
|
|
112
|
+
/** One turn's file-mutation group for the turn-tail card. */
|
|
113
|
+
export interface FilePreviewTurnGroup {
|
|
114
|
+
readonly turn: number;
|
|
115
|
+
readonly files: readonly FilePreviewTurnFile[];
|
|
116
|
+
}
|
|
117
|
+
/** Whole-response of `filePreview.turnFiles`: every turn's mutation groups. */
|
|
118
|
+
export interface FilePreviewTurnMap {
|
|
119
|
+
/** Files mutated per turn, in event order within each turn. */
|
|
120
|
+
readonly turns: readonly FilePreviewTurnGroup[];
|
|
121
|
+
/** Seq of the last scanned session event (cache invalidation watermark). */
|
|
122
|
+
readonly asOfSeq: number;
|
|
123
|
+
}
|
|
124
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@khorsheed/dsh-file-preview",
|
|
3
|
+
"description": "Read-only file-preview Remote service: lists the files a session wrote or edited and serves their current text content",
|
|
4
|
+
"keywords": [
|
|
5
|
+
"dsh",
|
|
6
|
+
"dsh-plugin",
|
|
7
|
+
"cordis",
|
|
8
|
+
"file-preview"
|
|
9
|
+
],
|
|
10
|
+
"version": "0.1.0",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "lib/index.js",
|
|
13
|
+
"types": "lib/types/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./lib/types/index.d.ts",
|
|
17
|
+
"default": "./lib/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./invariant": {
|
|
20
|
+
"types": "./lib/types/invariant.d.ts",
|
|
21
|
+
"default": "./lib/invariant.js"
|
|
22
|
+
},
|
|
23
|
+
"./types": {
|
|
24
|
+
"types": "./lib/types/types.d.ts",
|
|
25
|
+
"default": "./lib/types/types.js"
|
|
26
|
+
},
|
|
27
|
+
"./typert": {
|
|
28
|
+
"types": "./lib/typert.host.d.ts",
|
|
29
|
+
"default": "./lib/typert.host.js"
|
|
30
|
+
},
|
|
31
|
+
"./remote": {
|
|
32
|
+
"types": "./lib/typert.remote-client.d.ts",
|
|
33
|
+
"default": "./lib/typert.remote-client.js"
|
|
34
|
+
},
|
|
35
|
+
"./src/*": "./src/*",
|
|
36
|
+
"./package.json": "./package.json"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"lib",
|
|
40
|
+
"lib/typert.host.d.ts",
|
|
41
|
+
"lib/typert.host.js",
|
|
42
|
+
"lib/typert.remote-client.d.ts",
|
|
43
|
+
"lib/typert.remote-client.js",
|
|
44
|
+
"cordis.patch.yml",
|
|
45
|
+
"skills/**/*.md",
|
|
46
|
+
"LICENSE",
|
|
47
|
+
"CHANGELOG.md"
|
|
48
|
+
],
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@deepseek-ai/dsh-agent": "^0.1.0-rc.6",
|
|
52
|
+
"@deepseek-ai/dsh-fs": "^0.1.0-rc.6",
|
|
53
|
+
"@deepseek-ai/dsh-invariants": "^0.1.0-rc.6",
|
|
54
|
+
"@deepseek-ai/dsh-native-command": "^0.1.0-rc.6",
|
|
55
|
+
"@deepseek-ai/dsh-session": "^0.1.0-rc.6",
|
|
56
|
+
"@deepseek-ai/dsh-tools": "^0.1.0-rc.6",
|
|
57
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.0-rc.6",
|
|
58
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@deepseek-ai/dsh-agent": "^0.1.1-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-fs": "^0.1.1-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-invariants": "^0.1.1-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-llm": "^0.1.1-rc.1",
|
|
65
|
+
"@deepseek-ai/dsh-loader-smoke": "^0.1.1-rc.1",
|
|
66
|
+
"@deepseek-ai/dsh-native-command": "^0.1.1-rc.1",
|
|
67
|
+
"@deepseek-ai/dsh-session": "^0.1.1-rc.1",
|
|
68
|
+
"@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
|
|
69
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.1.1-rc.1",
|
|
70
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
71
|
+
},
|
|
72
|
+
"dsh": {
|
|
73
|
+
"bundle": {
|
|
74
|
+
"patch": "./cordis.patch.yml"
|
|
75
|
+
},
|
|
76
|
+
"compat": {
|
|
77
|
+
"minHost": "0.1.0-rc.6",
|
|
78
|
+
"verifiedHost": "0.1.1-rc.2"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 3d-artifact
|
|
3
|
+
description: Generate sandbox-runnable interactive 3D / digital-twin single-file HTML that obeys the strict sandbox CSP — self-contained, zero runtime network, GLB-inline zero-fetch models. Load when the task asks for a 3D visualization, simulation, digital twin, or WebGL scene delivered as one standalone HTML page.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 3d-artifact — generating sandbox-runnable interactive 3D / digital-twin single-file HTML
|
|
7
|
+
|
|
8
|
+
## When to load
|
|
9
|
+
|
|
10
|
+
Load this skill when the task asks to produce a **self-contained HTML page that renders 3D content** — visualizations, simulations, digital twins, WebGL scenes (three.js, etc.) — that must run inside a sandboxed preview iframe under a strict CSP (e.g. the dsh file-view / deliverables preview).
|
|
11
|
+
|
|
12
|
+
## The hard contract — violations fail silently in the sandbox; this is the #1 cause of "dead" artifacts
|
|
13
|
+
|
|
14
|
+
1. **Single file, fully self-contained.** All JS/CSS inline. Images as `data:` URIs. No external stylesheets or fonts.
|
|
15
|
+
2. **Zero runtime network.** No `fetch` / XHR / WebSocket / EventSource. Never `loader.load(url)`.
|
|
16
|
+
- 3D models MUST be inlined as **GLB base64** and decoded by hand: `atob` → `Uint8Array` → `GLTFLoader.parse(arrayBuffer, ...)`.
|
|
17
|
+
- **Never** embed a `.gltf` JSON whose buffer uses a `data:...` URI. three.js's GLTFLoader resolves `data:` URIs through `fetch`, which the sandbox CSP (`connect-src 'none'`) blocks — verified against three r152.
|
|
18
|
+
3. **Libraries may load only from whitelisted CDNs** (the CSP's `script-src` allows exactly these): `https://cdn.jsdelivr.net` and `https://cdnjs.cloudflare.com`. Use ESM + an inline `<script type="importmap">`. Never derive a resource URL from user/data input at runtime.
|
|
19
|
+
4. **Embed this Tier1 CSP meta tag** in `<head>`:
|
|
20
|
+
|
|
21
|
+
```html
|
|
22
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; connect-src 'none'; worker-src blob:; object-src 'none'; base-uri 'none'; form-action 'none'; upgrade-insecure-requests; block-all-mixed-content">
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
5. **Size red lines**: GLB ≤ 6 MB raw (≈8 MB base64); total page ≤ 16 MB. Base64 inflates ×4/3. Prefer procedural geometry, SVG, and compressed textures (KTX2/DRACO) over big assets.
|
|
26
|
+
6. **Performance**: cap `renderer.setPixelRatio` (≤2); keep the scene interactive within ~1 s of load; prefer `InstancedMesh` for repeated objects (vehicles, clamps, turbines); defer heavy DOM with `content-visibility`-style techniques; merge many small geometries (e.g. `BufferGeometryUtils.mergeGeometries`).
|
|
27
|
+
|
|
28
|
+
## Recommended stack
|
|
29
|
+
|
|
30
|
+
- three.js ESM + OrbitControls via the importmap pattern:
|
|
31
|
+
|
|
32
|
+
```html
|
|
33
|
+
<script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.152.2/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.152.2/examples/jsm/"}}</script>
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- Procedural geometry + vertex colors beat downloaded assets for reliability and size.
|
|
37
|
+
|
|
38
|
+
## Self-check before finishing
|
|
39
|
+
|
|
40
|
+
- [ ] No external URL anywhere except whitelisted CDN scripts used by the importmap.
|
|
41
|
+
- [ ] No `fetch(` / `XMLHttpRequest` / `WebSocket` / `loader.load(` anywhere in the page.
|
|
42
|
+
- [ ] Any model is GLB base64 decoded via `atob` → `parse(arrayBuffer)` — never a `data:` URI buffer.
|
|
43
|
+
- [ ] The Tier1 CSP meta tag above is present in `<head>`.
|
|
44
|
+
- [ ] Page + all inline assets ≤ 16 MB.
|
|
45
|
+
- [ ] Opens and renders in a plain browser within ~2 s — that is the sandbox experience.
|
|
46
|
+
|
|
47
|
+
## Authoring tips for realism (超写实 / 数字孪生 scenes)
|
|
48
|
+
|
|
49
|
+
- Golden-hour lighting: warm directional sun + hemisphere fill + exponential fog for depth.
|
|
50
|
+
- ACES filmic tone mapping + sRGB output (`renderer.outputEncoding`).
|
|
51
|
+
- Layered composition: hero object (bridge), midground (valley, road), background (mountain silhouettes, turbines, power towers), atmosphere (fog, mist).
|
|
52
|
+
- Digital-twin overlays read best as translucent DOM panels + in-scene markers (sensor nodes, point clouds, BIM wireframes, heatmaps, drone paths).
|