openplanr 2.2639.4 → 2.2640.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 +15 -0
- package/dist/cli/commands/github.d.ts.map +1 -1
- package/dist/cli/commands/github.js +4 -3
- package/dist/cli/commands/github.js.map +1 -1
- package/dist/dashboard/.vite/manifest.json +1 -1
- package/dist/dashboard/assets/{index-p83hrHZD.js → index-9DLb9EO5.js} +2 -2
- package/dist/dashboard/assets/{index-p83hrHZD.js.map → index-9DLb9EO5.js.map} +1 -1
- package/dist/dashboard/dashboard-manifest.json +13 -13
- package/dist/dashboard/index.html +1 -1
- package/dist/models/schema.d.ts.map +1 -1
- package/dist/models/schema.js +24 -1
- package/dist/models/schema.js.map +1 -1
- package/dist/services/artifact-service.js +2 -2
- package/dist/services/artifact-service.js.map +1 -1
- package/dist/services/github-service.d.ts +6 -4
- package/dist/services/github-service.d.ts.map +1 -1
- package/dist/services/github-service.js +7 -2
- package/dist/services/github-service.js.map +1 -1
- package/dist/services/id-service.d.ts +4 -1
- package/dist/services/id-service.d.ts.map +1 -1
- package/dist/services/id-service.js +28 -15
- package/dist/services/id-service.js.map +1 -1
- package/dist/services/landing/receipt-custody.d.ts.map +1 -1
- package/dist/services/landing/receipt-custody.js +2 -0
- package/dist/services/landing/receipt-custody.js.map +1 -1
- package/dist/services/report-linter-service.d.ts.map +1 -1
- package/dist/services/report-linter-service.js +2 -4
- package/dist/services/report-linter-service.js.map +1 -1
- package/docs/CLI.md +2 -0
- package/lib/host-packages/adapter-registry.json +5 -5
- package/lib/host-packages/capability-map.json +1 -1
- package/lib/host-packages/claude/.claude-plugin/marketplace.json +2 -2
- package/lib/host-packages/claude/openplanr/.claude-plugin/icon.svg +11 -0
- package/lib/host-packages/claude/openplanr/.claude-plugin/plugin.json +8 -3
- package/lib/host-packages/claude/openplanr/.openplanr-content.json +160 -28
- package/lib/host-packages/claude/openplanr/README.md +4 -3
- package/lib/host-packages/claude/openplanr/skills/ceo-review/SKILL.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/chair-review/SKILL.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/challenger-review/SKILL.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/cmo-review/SKILL.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/coo-review/SKILL.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/cpo-review/SKILL.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/cto-review/SKILL.md +1 -1
- package/lib/host-packages/claude/openplanr/skills/design/openplanr.skill.json +88 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design.mjs +83 -21459
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/claude/openplanr/skills/design-loop/openplanr.skill.json +88 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design.mjs +83 -21459
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/claude/openplanr/skills/design-review/openplanr.skill.json +88 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design.mjs +83 -21459
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/claude/openplanr/skills/plan/openplanr.skill.json +88 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design.mjs +83 -21459
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/claude/openplanr/skills/sync/SKILL.md +17 -6
- package/lib/host-packages/claude/openplanr/skills/sync/scripts/sync.mjs +7 -104
- package/lib/host-packages/cursor/openplanr/.openplanr-content.json +144 -16
- package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design.mjs +83 -21459
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design.mjs +83 -21459
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design.mjs +83 -21459
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design.mjs +83 -21459
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-sync/scripts/sync.mjs +7 -104
- package/lib/host-packages/cursor/openplanr/rules/planr-sync.mdc +17 -6
- package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
- package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +3 -2
- package/lib/host-packages/openai/openplanr/.openplanr-content.json +155 -27
- package/lib/host-packages/openai/openplanr/skills/ceo-review/SKILL.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/chair-review/SKILL.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/challenger-review/SKILL.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/cmo-review/SKILL.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/coo-review/SKILL.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/cpo-review/SKILL.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/cto-review/SKILL.md +1 -1
- package/lib/host-packages/openai/openplanr/skills/design/openplanr.skill.json +88 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design.mjs +83 -21459
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/openai/openplanr/skills/design-loop/openplanr.skill.json +88 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design.mjs +83 -21459
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/openai/openplanr/skills/design-review/openplanr.skill.json +88 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design.mjs +83 -21459
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/openai/openplanr/skills/plan/openplanr.skill.json +88 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-artifact-shell.mjs +2506 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-document.mjs +3674 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-entities.mjs +489 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-parse5-parser.mjs +3970 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-parse5-tokenizer.mjs +3501 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-review-handoff.mjs +4408 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-sandbox-guards.mjs +1073 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design-share.mjs +2127 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design.mjs +83 -21459
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/studio.js +1 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/protocol/package.json +1 -1
- package/lib/host-packages/openai/openplanr/skills/sync/SKILL.md +17 -6
- package/lib/host-packages/openai/openplanr/skills/sync/scripts/sync.mjs +7 -104
- package/lib/integrations.d.mts +0 -10
- package/lib/integrations.mjs +7 -104
- package/package.json +2 -2
|
@@ -0,0 +1,2506 @@
|
|
|
1
|
+
// packages/artifact/lib/artifact/envelope.mjs
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
|
|
4
|
+
// packages/protocol/src/errors.mjs
|
|
5
|
+
var ARTIFACT_ERROR_CODES = Object.freeze({
|
|
6
|
+
INPUT_INVALID: "E_ARTIFACT_INPUT_INVALID",
|
|
7
|
+
FILE_MISSING: "E_ARTIFACT_FILE_MISSING",
|
|
8
|
+
ROOT_MISSING: "E_ARTIFACT_ROOT_MISSING",
|
|
9
|
+
PATH_TRAVERSAL: "E_ARTIFACT_PATH_TRAVERSAL",
|
|
10
|
+
SYMLINK_ESCAPE: "E_ARTIFACT_SYMLINK_ESCAPE",
|
|
11
|
+
EXTERNAL_ASSET: "E_ARTIFACT_EXTERNAL_ASSET",
|
|
12
|
+
UNRESOLVED_ASSET: "E_ARTIFACT_UNRESOLVED_ASSET",
|
|
13
|
+
UNSUPPORTED_MODULE: "E_ARTIFACT_UNSUPPORTED_MODULE",
|
|
14
|
+
UNSAFE_HTML: "E_ARTIFACT_UNSAFE_HTML",
|
|
15
|
+
FILE_LIMIT: "E_ARTIFACT_FILE_LIMIT",
|
|
16
|
+
BYTE_LIMIT: "E_ARTIFACT_BYTE_LIMIT",
|
|
17
|
+
OUTPUT_LIMIT: "E_ARTIFACT_OUTPUT_LIMIT",
|
|
18
|
+
REDACTION: "E_ARTIFACT_REDACTION",
|
|
19
|
+
ENVELOPE_INVALID: "E_ARTIFACT_ENVELOPE_INVALID",
|
|
20
|
+
SCHEMA_UNSUPPORTED: "E_ARTIFACT_SCHEMA_UNSUPPORTED",
|
|
21
|
+
LOOPBACK_BIND: "E_ARTIFACT_LOOPBACK_BIND",
|
|
22
|
+
LOOPBACK_STATE: "E_ARTIFACT_LOOPBACK_STATE",
|
|
23
|
+
PORT_IN_USE: "E_ARTIFACT_PORT_IN_USE",
|
|
24
|
+
SESSION_TOKEN: "E_ARTIFACT_SESSION_TOKEN",
|
|
25
|
+
SESSION_NOT_FOUND: "E_ARTIFACT_SESSION_NOT_FOUND",
|
|
26
|
+
REQUEST_INVALID: "E_ARTIFACT_REQUEST_INVALID",
|
|
27
|
+
REQUEST_LIMIT: "E_ARTIFACT_REQUEST_LIMIT",
|
|
28
|
+
SANDBOX_POLICY: "E_ARTIFACT_SANDBOX_POLICY",
|
|
29
|
+
BRIDGE_INVALID: "E_ARTIFACT_BRIDGE_INVALID",
|
|
30
|
+
REVIEW_INVALID: "E_ARTIFACT_REVIEW_INVALID",
|
|
31
|
+
REVIEW_WRITE: "E_ARTIFACT_REVIEW_WRITE",
|
|
32
|
+
REVIEW_IMPORT: "E_ARTIFACT_REVIEW_IMPORT",
|
|
33
|
+
REVIEW_DECODER_REQUIRED: "E_ARTIFACT_REVIEW_DECODER_REQUIRED",
|
|
34
|
+
REVIEW_EXPORT: "E_ARTIFACT_REVIEW_EXPORT",
|
|
35
|
+
DIGEST_MISMATCH: "E_ARTIFACT_DIGEST_MISMATCH",
|
|
36
|
+
STALE_REVIEW: "E_ARTIFACT_STALE_REVIEW",
|
|
37
|
+
MERGE_CONFLICT: "E_ARTIFACT_MERGE_CONFLICT",
|
|
38
|
+
CODEC_INVALID: "E_ARTIFACT_CODEC_INVALID",
|
|
39
|
+
CODEC_UNAVAILABLE: "E_ARTIFACT_CODEC_UNAVAILABLE",
|
|
40
|
+
CODEC_FAILED: "E_ARTIFACT_CODEC_FAILED",
|
|
41
|
+
FRAGMENT_INVALID: "E_ARTIFACT_FRAGMENT_INVALID",
|
|
42
|
+
FRAGMENT_VERSION_UNSUPPORTED: "E_ARTIFACT_FRAGMENT_VERSION_UNSUPPORTED",
|
|
43
|
+
FRAGMENT_TOO_LARGE: "E_ARTIFACT_FRAGMENT_TOO_LARGE",
|
|
44
|
+
DECOMPRESSION_LIMIT: "E_ARTIFACT_DECOMPRESSION_LIMIT",
|
|
45
|
+
BROWSER_UNSUPPORTED: "E_ARTIFACT_BROWSER_UNSUPPORTED",
|
|
46
|
+
ENCRYPTION_FAILED: "E_ARTIFACT_ENCRYPTION_FAILED",
|
|
47
|
+
DECRYPTION_FAILED: "E_ARTIFACT_DECRYPTION_FAILED",
|
|
48
|
+
CONFIRMATION_REQUIRED: "E_ARTIFACT_CONFIRMATION_REQUIRED",
|
|
49
|
+
SHORT_CONFIRMATION_REQUIRED: "E_ARTIFACT_SHORT_CONFIRMATION_REQUIRED",
|
|
50
|
+
PASTE_INVALID: "E_ARTIFACT_PASTE_INVALID",
|
|
51
|
+
PASTE_UNAVAILABLE: "E_ARTIFACT_PASTE_UNAVAILABLE",
|
|
52
|
+
PASTE_EXPIRED: "E_ARTIFACT_PASTE_EXPIRED",
|
|
53
|
+
PASTE_LIMIT: "E_ARTIFACT_PASTE_LIMIT",
|
|
54
|
+
SHARE_NETWORK: "E_ARTIFACT_SHARE_NETWORK",
|
|
55
|
+
ROOM_CREATE_AMBIGUOUS: "E_ARTIFACT_ROOM_CREATE_AMBIGUOUS",
|
|
56
|
+
ROOM_INVALID: "E_ARTIFACT_ROOM_INVALID",
|
|
57
|
+
ROOM_UNAVAILABLE: "E_ARTIFACT_ROOM_UNAVAILABLE",
|
|
58
|
+
ROOM_EXPIRED: "E_ARTIFACT_ROOM_EXPIRED",
|
|
59
|
+
ROOM_CLOSED: "E_ARTIFACT_ROOM_CLOSED",
|
|
60
|
+
ROOM_LEGACY_READ_ONLY: "E_ARTIFACT_ROOM_LEGACY_READ_ONLY",
|
|
61
|
+
ROOM_FORBIDDEN: "E_ARTIFACT_ROOM_FORBIDDEN",
|
|
62
|
+
ROOM_EVENT_INVALID: "E_ARTIFACT_ROOM_EVENT_INVALID",
|
|
63
|
+
ROOM_EVENT_REPLAY: "E_ARTIFACT_ROOM_EVENT_REPLAY"
|
|
64
|
+
});
|
|
65
|
+
var PROTOCOL_ERROR_CODES = Object.freeze({
|
|
66
|
+
ASSET_NOT_FOUND: "E_PROTOCOL_ASSET_NOT_FOUND",
|
|
67
|
+
DIGEST_MISMATCH: "E_PROTOCOL_DIGEST_MISMATCH",
|
|
68
|
+
DOCUMENT_INVALID: "E_PROTOCOL_DOCUMENT_INVALID",
|
|
69
|
+
DOCUMENT_VERSION_UNSUPPORTED: "E_PROTOCOL_DOCUMENT_VERSION_UNSUPPORTED",
|
|
70
|
+
DUPLICATE_ID: "E_PROTOCOL_DUPLICATE_ID",
|
|
71
|
+
REFERENCE_INVALID: "E_PROTOCOL_REFERENCE_INVALID",
|
|
72
|
+
REGISTRY_INVALID: "E_PROTOCOL_REGISTRY_INVALID",
|
|
73
|
+
SCHEMA_INVALID: "E_PROTOCOL_SCHEMA_INVALID",
|
|
74
|
+
SCHEMA_UNKNOWN: "E_SCHEMA_UNKNOWN",
|
|
75
|
+
SCHEMA_VERSION_UNSUPPORTED: "E_SCHEMA_VERSION_UNSUPPORTED",
|
|
76
|
+
SORT_ORDER_INVALID: "E_PROTOCOL_SORT_ORDER_INVALID"
|
|
77
|
+
});
|
|
78
|
+
var PipelineError = class extends Error {
|
|
79
|
+
constructor(code, message, fix = "", details = void 0) {
|
|
80
|
+
super(message);
|
|
81
|
+
this.name = "PipelineError";
|
|
82
|
+
this.code = code;
|
|
83
|
+
this.fix = fix;
|
|
84
|
+
if (details !== void 0) this.details = details;
|
|
85
|
+
}
|
|
86
|
+
toJSON() {
|
|
87
|
+
return {
|
|
88
|
+
ok: false,
|
|
89
|
+
code: this.code,
|
|
90
|
+
problem: this.message,
|
|
91
|
+
...this.fix ? { fix: this.fix } : {},
|
|
92
|
+
...this.details === void 0 ? {} : { details: this.details }
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// packages/artifact/lib/artifact/internal/schema-loader.mjs
|
|
98
|
+
import { fileURLToPath as __planrAssetFile } from "node:url";
|
|
99
|
+
import { readFileSync } from "node:fs";
|
|
100
|
+
import { createRequire } from "node:module";
|
|
101
|
+
import { dirname, join } from "node:path";
|
|
102
|
+
|
|
103
|
+
// packages/protocol/src/json-schema.mjs
|
|
104
|
+
var typeOf = (v) => {
|
|
105
|
+
if (v === null) return "null";
|
|
106
|
+
if (Array.isArray(v)) return "array";
|
|
107
|
+
if (Number.isInteger(v)) return "integer";
|
|
108
|
+
if (typeof v === "number") return "number";
|
|
109
|
+
return typeof v;
|
|
110
|
+
};
|
|
111
|
+
var matchesType = (v, t) => {
|
|
112
|
+
if (t === "integer") return Number.isInteger(v);
|
|
113
|
+
if (t === "number") return typeof v === "number";
|
|
114
|
+
if (t === "string") return typeof v === "string";
|
|
115
|
+
if (t === "boolean") return typeof v === "boolean";
|
|
116
|
+
if (t === "null") return v === null;
|
|
117
|
+
if (t === "array") return Array.isArray(v);
|
|
118
|
+
if (t === "object") return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
119
|
+
return false;
|
|
120
|
+
};
|
|
121
|
+
var FORMAT_DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
122
|
+
var FORMAT_DATETIME = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
123
|
+
function resolveJsonPointer(root, reference) {
|
|
124
|
+
if (reference === "#") return root;
|
|
125
|
+
if (!reference.startsWith("#/")) return null;
|
|
126
|
+
return reference.slice(2).split("/").map((part) => part.replaceAll("~1", "/").replaceAll("~0", "~")).reduce((value, part) => value?.[part], root);
|
|
127
|
+
}
|
|
128
|
+
var validateNode = (value, schema, path, errs, context) => {
|
|
129
|
+
if (schema === true) return;
|
|
130
|
+
if (schema === false) {
|
|
131
|
+
errs.push({ path, rule: "schema:false", detail: "value not allowed" });
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (typeof schema?.$ref === "string") {
|
|
135
|
+
const reference = schema.$ref;
|
|
136
|
+
let resolved;
|
|
137
|
+
let nextRoot = context.rootSchema;
|
|
138
|
+
let resolvedBase = context.base;
|
|
139
|
+
if (reference.startsWith("#")) {
|
|
140
|
+
resolved = resolveJsonPointer(context.rootSchema, reference);
|
|
141
|
+
} else if (typeof context.resolveRef === "function") {
|
|
142
|
+
const result = context.resolveRef(reference, { base: context.base });
|
|
143
|
+
resolved = result?.schema ?? result;
|
|
144
|
+
nextRoot = result?.rootSchema ?? resolved;
|
|
145
|
+
resolvedBase = result?.base ?? resolved?.$id ?? context.base;
|
|
146
|
+
}
|
|
147
|
+
if (!resolved) {
|
|
148
|
+
errs.push({ path, rule: "$ref", detail: `could not resolve schema reference ${reference}` });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
const referenceKey = `${context.base ?? "<root>"}:${reference}`;
|
|
152
|
+
if (context.referenceStack.includes(referenceKey)) {
|
|
153
|
+
errs.push({ path, rule: "$ref", detail: `circular schema reference ${reference}` });
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
validateNode(value, resolved, path, errs, {
|
|
157
|
+
...context,
|
|
158
|
+
rootSchema: nextRoot,
|
|
159
|
+
base: resolvedBase,
|
|
160
|
+
referenceStack: [...context.referenceStack, referenceKey]
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
if (schema.type !== void 0) {
|
|
164
|
+
const types = Array.isArray(schema.type) ? schema.type : [schema.type];
|
|
165
|
+
if (!types.some((t) => matchesType(value, t))) {
|
|
166
|
+
errs.push({
|
|
167
|
+
path,
|
|
168
|
+
rule: "type",
|
|
169
|
+
detail: `expected ${types.join("|")}, got ${typeOf(value)}`
|
|
170
|
+
});
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
if (schema.const !== void 0) {
|
|
175
|
+
if (value !== schema.const) {
|
|
176
|
+
errs.push({
|
|
177
|
+
path,
|
|
178
|
+
rule: "const",
|
|
179
|
+
detail: `expected ${JSON.stringify(schema.const)}, got ${JSON.stringify(value)}`
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (Array.isArray(schema.enum)) {
|
|
184
|
+
if (!schema.enum.includes(value)) {
|
|
185
|
+
errs.push({
|
|
186
|
+
path,
|
|
187
|
+
rule: "enum",
|
|
188
|
+
detail: `value ${JSON.stringify(value)} not in enum [${schema.enum.map((x) => JSON.stringify(x)).join(", ")}]`
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (typeof value === "string") {
|
|
193
|
+
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
194
|
+
errs.push({
|
|
195
|
+
path,
|
|
196
|
+
rule: "minLength",
|
|
197
|
+
detail: `length ${value.length} < ${schema.minLength}`
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (typeof schema.maxLength === "number" && value.length > schema.maxLength) {
|
|
201
|
+
errs.push({
|
|
202
|
+
path,
|
|
203
|
+
rule: "maxLength",
|
|
204
|
+
detail: `length ${value.length} > ${schema.maxLength}`
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
if (typeof schema.pattern === "string") {
|
|
208
|
+
try {
|
|
209
|
+
if (!new RegExp(schema.pattern).test(value)) {
|
|
210
|
+
errs.push({
|
|
211
|
+
path,
|
|
212
|
+
rule: "pattern",
|
|
213
|
+
detail: `value ${JSON.stringify(value)} does not match /${schema.pattern}/`
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
} catch (e) {
|
|
217
|
+
errs.push({
|
|
218
|
+
path,
|
|
219
|
+
rule: "pattern",
|
|
220
|
+
detail: `invalid regex /${schema.pattern}/: ${e instanceof Error ? e.message : String(e)}`
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (typeof schema.format === "string") {
|
|
225
|
+
if (schema.format === "date" && !FORMAT_DATE.test(value)) {
|
|
226
|
+
errs.push({
|
|
227
|
+
path,
|
|
228
|
+
rule: "format:date",
|
|
229
|
+
detail: `value ${JSON.stringify(value)} is not YYYY-MM-DD`
|
|
230
|
+
});
|
|
231
|
+
} else if (schema.format === "date-time" && !FORMAT_DATETIME.test(value)) {
|
|
232
|
+
errs.push({
|
|
233
|
+
path,
|
|
234
|
+
rule: "format:date-time",
|
|
235
|
+
detail: `value ${JSON.stringify(value)} is not ISO 8601 date-time`
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (typeof value === "number") {
|
|
241
|
+
if (typeof schema.minimum === "number" && value < schema.minimum) {
|
|
242
|
+
errs.push({ path, rule: "minimum", detail: `value ${value} < ${schema.minimum}` });
|
|
243
|
+
}
|
|
244
|
+
if (typeof schema.maximum === "number" && value > schema.maximum) {
|
|
245
|
+
errs.push({ path, rule: "maximum", detail: `value ${value} > ${schema.maximum}` });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (Array.isArray(value)) {
|
|
249
|
+
if (typeof schema.minItems === "number" && value.length < schema.minItems) {
|
|
250
|
+
errs.push({ path, rule: "minItems", detail: `length ${value.length} < ${schema.minItems}` });
|
|
251
|
+
}
|
|
252
|
+
if (typeof schema.maxItems === "number" && value.length > schema.maxItems) {
|
|
253
|
+
errs.push({ path, rule: "maxItems", detail: `length ${value.length} > ${schema.maxItems}` });
|
|
254
|
+
}
|
|
255
|
+
const prefixLength = Array.isArray(schema.prefixItems) ? schema.prefixItems.length : 0;
|
|
256
|
+
if (prefixLength > 0) {
|
|
257
|
+
for (let i = 0; i < Math.min(value.length, prefixLength); i++) {
|
|
258
|
+
validateNode(value[i], schema.prefixItems[i], `${path}[${i}]`, errs, context);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
if (schema.items !== void 0) {
|
|
262
|
+
for (let i = prefixLength; i < value.length; i++) {
|
|
263
|
+
validateNode(value[i], schema.items, `${path}[${i}]`, errs, context);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
if (schema.uniqueItems === true) {
|
|
267
|
+
const seen = /* @__PURE__ */ new Set();
|
|
268
|
+
for (const item of value) {
|
|
269
|
+
const key = JSON.stringify(item);
|
|
270
|
+
if (seen.has(key)) {
|
|
271
|
+
errs.push({ path, rule: "uniqueItems", detail: `duplicate item ${key}` });
|
|
272
|
+
break;
|
|
273
|
+
}
|
|
274
|
+
seen.add(key);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (schema.contains !== void 0) {
|
|
278
|
+
let matches = 0;
|
|
279
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
280
|
+
const containedErrors = [];
|
|
281
|
+
validateNode(value[index], schema.contains, `${path}[${index}]`, containedErrors, context);
|
|
282
|
+
if (containedErrors.length === 0) matches += 1;
|
|
283
|
+
}
|
|
284
|
+
const minimum = Number.isSafeInteger(schema.minContains) ? schema.minContains : 1;
|
|
285
|
+
const maximum = Number.isSafeInteger(schema.maxContains) ? schema.maxContains : null;
|
|
286
|
+
if (matches < minimum) {
|
|
287
|
+
errs.push({
|
|
288
|
+
path,
|
|
289
|
+
rule: "contains",
|
|
290
|
+
detail: `matched ${matches} contained items; expected at least ${minimum}`
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
if (maximum !== null && matches > maximum) {
|
|
294
|
+
errs.push({
|
|
295
|
+
path,
|
|
296
|
+
rule: "contains",
|
|
297
|
+
detail: `matched ${matches} contained items; expected at most ${maximum}`
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
303
|
+
if (Array.isArray(schema.required)) {
|
|
304
|
+
for (const req of schema.required) {
|
|
305
|
+
if (!Object.hasOwn(value, req)) {
|
|
306
|
+
errs.push({ path, rule: "required", detail: `missing required property '${req}'` });
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
const props = schema.properties || {};
|
|
311
|
+
for (const [k, v] of Object.entries(value)) {
|
|
312
|
+
if (!Object.hasOwn(props, k)) {
|
|
313
|
+
if (schema.additionalProperties === false) {
|
|
314
|
+
errs.push({ path, rule: "additionalProperties", detail: `unknown property '${k}'` });
|
|
315
|
+
} else if (schema.additionalProperties === true || schema.additionalProperties !== null && typeof schema.additionalProperties === "object") {
|
|
316
|
+
validateNode(v, schema.additionalProperties, `${path}.${k}`, errs, context);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
for (const [k, v] of Object.entries(value)) {
|
|
321
|
+
if (Object.hasOwn(props, k)) validateNode(v, props[k], `${path}.${k}`, errs, context);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (Array.isArray(schema.oneOf)) {
|
|
325
|
+
let matched = 0;
|
|
326
|
+
for (const sub of schema.oneOf) {
|
|
327
|
+
const e = [];
|
|
328
|
+
validateNode(value, sub, path, e, context);
|
|
329
|
+
if (e.length === 0) matched++;
|
|
330
|
+
}
|
|
331
|
+
if (matched !== 1) {
|
|
332
|
+
const titles = schema.oneOf.map((s) => s.title || "(unnamed)").join(" | ");
|
|
333
|
+
errs.push({
|
|
334
|
+
path,
|
|
335
|
+
rule: "oneOf",
|
|
336
|
+
detail: `matched ${matched}/${schema.oneOf.length} branches (expected exactly 1). Branches: ${titles}`
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
if (Array.isArray(schema.anyOf)) {
|
|
341
|
+
let matched = 0;
|
|
342
|
+
for (const sub of schema.anyOf) {
|
|
343
|
+
const candidateErrors = [];
|
|
344
|
+
validateNode(value, sub, path, candidateErrors, context);
|
|
345
|
+
if (candidateErrors.length === 0) matched += 1;
|
|
346
|
+
}
|
|
347
|
+
if (matched === 0) {
|
|
348
|
+
const titles = schema.anyOf.map((sub) => sub.title || "(unnamed)").join(" | ");
|
|
349
|
+
errs.push({
|
|
350
|
+
path,
|
|
351
|
+
rule: "anyOf",
|
|
352
|
+
detail: `matched 0/${schema.anyOf.length} branches (expected at least 1). Branches: ${titles}`
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
if (Array.isArray(schema.allOf)) {
|
|
357
|
+
for (const sub of schema.allOf) {
|
|
358
|
+
validateNode(value, sub, path, errs, context);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
if (schema.if !== void 0) {
|
|
362
|
+
const conditionErrors = [];
|
|
363
|
+
validateNode(value, schema.if, path, conditionErrors, context);
|
|
364
|
+
const branch = conditionErrors.length === 0 ? schema.then : schema.else;
|
|
365
|
+
if (branch !== void 0) validateNode(value, branch, path, errs, context);
|
|
366
|
+
}
|
|
367
|
+
if (schema.not !== void 0) {
|
|
368
|
+
const e = [];
|
|
369
|
+
validateNode(value, schema.not, path, e, context);
|
|
370
|
+
if (e.length === 0) {
|
|
371
|
+
errs.push({ path, rule: "not", detail: "value matched a forbidden subschema" });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
var validateJson = (value, schema, {
|
|
376
|
+
resolveRef,
|
|
377
|
+
base = (
|
|
378
|
+
/** @type {{ $id?: string } | null | undefined} */
|
|
379
|
+
schema?.$id ?? null
|
|
380
|
+
)
|
|
381
|
+
} = {}) => {
|
|
382
|
+
const errs = [];
|
|
383
|
+
validateNode(value, schema, "$", errs, {
|
|
384
|
+
rootSchema: schema,
|
|
385
|
+
resolveRef,
|
|
386
|
+
base,
|
|
387
|
+
referenceStack: []
|
|
388
|
+
});
|
|
389
|
+
return errs;
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// packages/artifact/lib/artifact/internal/schema-loader.mjs
|
|
393
|
+
var require2 = createRequire(new URL("./runtime/packages/artifact/lib/artifact/internal/schema-loader.mjs", import.meta.url).href);
|
|
394
|
+
var protocolPackageRoot = dirname(__planrAssetFile(new URL("./runtime/packages/protocol/package.json", import.meta.url)));
|
|
395
|
+
var SCHEMAS_ROOT = join(protocolPackageRoot, "schemas");
|
|
396
|
+
var DEFAULT_SCHEMA_VERSIONS = Object.freeze(["v1.0.0", "v1.1.0"]);
|
|
397
|
+
var schemaCache = /* @__PURE__ */ new Map();
|
|
398
|
+
function parseSchemaReference(name, version) {
|
|
399
|
+
if (typeof name !== "string" || !/^[a-z0-9-]+(?:\.schema\.json)?$/i.test(name)) {
|
|
400
|
+
throw new Error(`invalid schema name: ${String(name)}`);
|
|
401
|
+
}
|
|
402
|
+
const cleanName = name.replace(/\.schema\.json$/i, "");
|
|
403
|
+
if (version !== void 0 && !/^v\d+\.\d+\.\d+$/.test(version)) {
|
|
404
|
+
throw new Error(`invalid schema version: ${String(version)}`);
|
|
405
|
+
}
|
|
406
|
+
return { cleanName, versions: version ? [version] : DEFAULT_SCHEMA_VERSIONS };
|
|
407
|
+
}
|
|
408
|
+
function loadSchema(name, version = void 0) {
|
|
409
|
+
if (typeof name === "string" && name.includes("/")) {
|
|
410
|
+
const [qualifiedVersion, qualifiedName, ...rest] = name.split("/");
|
|
411
|
+
if (rest.length > 0 || version !== void 0)
|
|
412
|
+
throw new Error(`invalid schema reference: ${name}`);
|
|
413
|
+
return loadSchema(qualifiedName, qualifiedVersion);
|
|
414
|
+
}
|
|
415
|
+
const { cleanName, versions } = parseSchemaReference(name, version);
|
|
416
|
+
for (const candidateVersion of versions) {
|
|
417
|
+
const key = `${candidateVersion}/${cleanName}`;
|
|
418
|
+
if (schemaCache.has(key)) return schemaCache.get(key);
|
|
419
|
+
const path = join(SCHEMAS_ROOT, candidateVersion, `${cleanName}.schema.json`);
|
|
420
|
+
try {
|
|
421
|
+
const schema = JSON.parse(readFileSync(path, "utf-8"));
|
|
422
|
+
schemaCache.set(key, schema);
|
|
423
|
+
return schema;
|
|
424
|
+
} catch (error) {
|
|
425
|
+
if (error?.code !== "ENOENT") throw error;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`unknown schema: ${cleanName}${version ? ` (${version})` : ""}`);
|
|
429
|
+
}
|
|
430
|
+
function validate(data, schemaName, version = void 0) {
|
|
431
|
+
return validateJson(data, loadSchema(schemaName, version));
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// packages/artifact/lib/artifact/envelope.mjs
|
|
435
|
+
var textEncoder = new TextEncoder();
|
|
436
|
+
var SHA256_RE = /^[a-f0-9]{64}$/;
|
|
437
|
+
var ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
438
|
+
var MAX_ARTIFACTS = 256;
|
|
439
|
+
var MAX_ARTIFACT_HTML_BYTES = 10 * 1024 * 1024;
|
|
440
|
+
var MAX_PINS = 1e4;
|
|
441
|
+
var MAX_REPLIES = 1e4;
|
|
442
|
+
var MAX_PASTE_BYTES = 5 * 1024 * 1024;
|
|
443
|
+
var ARTIFACT_PRESENTATIONS = Object.freeze(["document", "canvas"]);
|
|
444
|
+
var ARTIFACT_DOCUMENT_MAX_HEIGHT = 262144;
|
|
445
|
+
function invalid(message, details = void 0) {
|
|
446
|
+
throw new PipelineError(ARTIFACT_ERROR_CODES.ENVELOPE_INVALID, message, "", details);
|
|
447
|
+
}
|
|
448
|
+
function assertBoundedString(value, label, { min = 0, max, pattern } = {}) {
|
|
449
|
+
if (typeof value !== "string" || value.length < min || max !== void 0 && value.length > max || pattern && !pattern.test(value)) {
|
|
450
|
+
invalid(`${label} is outside its allowed string bounds.`);
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
function assertViewport(viewport, label, { maxHeight = 16384 } = {}) {
|
|
454
|
+
if (!viewport || !Number.isInteger(viewport.width) || !Number.isInteger(viewport.height) || viewport.width < 1 || viewport.width > 16384 || viewport.height < 1 || viewport.height > maxHeight) {
|
|
455
|
+
invalid(
|
|
456
|
+
`${label} must have an integer width from 1 through 16384 pixels and height from 1 through ${maxHeight} pixels.`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
function normalizeUtf8Text(value) {
|
|
461
|
+
if (typeof value !== "string") {
|
|
462
|
+
throw new PipelineError(ARTIFACT_ERROR_CODES.INPUT_INVALID, "Artifact HTML must be a string.");
|
|
463
|
+
}
|
|
464
|
+
return value.replace(/^\uFEFF/, "").replace(/\r\n?/g, "\n");
|
|
465
|
+
}
|
|
466
|
+
function canonicalArtifactBytes(html) {
|
|
467
|
+
return textEncoder.encode(normalizeUtf8Text(html));
|
|
468
|
+
}
|
|
469
|
+
function sha256Hex(bytes) {
|
|
470
|
+
return createHash("sha256").update(bytes).digest("hex");
|
|
471
|
+
}
|
|
472
|
+
function digestArtifact(html) {
|
|
473
|
+
return sha256Hex(canonicalArtifactBytes(html));
|
|
474
|
+
}
|
|
475
|
+
function canonicalObject(value) {
|
|
476
|
+
if (Array.isArray(value)) return value.map(canonicalObject);
|
|
477
|
+
if (!value || typeof value !== "object") return value;
|
|
478
|
+
return Object.fromEntries(
|
|
479
|
+
Object.keys(value).sort().map((key) => [key, canonicalObject(value[key])])
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
function canonicalSerialize(value) {
|
|
483
|
+
return JSON.stringify(canonicalObject(value));
|
|
484
|
+
}
|
|
485
|
+
function canonicalBytes(value) {
|
|
486
|
+
return textEncoder.encode(canonicalSerialize(value));
|
|
487
|
+
}
|
|
488
|
+
function normalizeViewport(viewport = {}) {
|
|
489
|
+
const width = viewport.width ?? 1440;
|
|
490
|
+
const height = viewport.height ?? 900;
|
|
491
|
+
if (!Number.isInteger(width) || width < 1 || width > 16384 || !Number.isInteger(height) || height < 1 || height > 16384) {
|
|
492
|
+
throw new PipelineError(
|
|
493
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
494
|
+
"Artifact viewport width and height must be integers from 1 through 16384."
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
return { width, height };
|
|
498
|
+
}
|
|
499
|
+
function normalizeArtifact(artifact) {
|
|
500
|
+
if (!artifact || typeof artifact !== "object") {
|
|
501
|
+
throw new PipelineError(
|
|
502
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
503
|
+
"Each artifact must be an object."
|
|
504
|
+
);
|
|
505
|
+
}
|
|
506
|
+
const id = artifact.id;
|
|
507
|
+
const title = artifact.title;
|
|
508
|
+
if (typeof id !== "string" || !ID_RE.test(id) || id.length > 128) {
|
|
509
|
+
throw new PipelineError(
|
|
510
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
511
|
+
`Invalid artifact id: ${String(id)}`
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
if (typeof title !== "string" || title.length === 0 || title.length > 512) {
|
|
515
|
+
throw new PipelineError(
|
|
516
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
517
|
+
`Artifact ${id} requires a title.`
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
const html = normalizeUtf8Text(artifact.html);
|
|
521
|
+
if (html.length === 0) {
|
|
522
|
+
throw new PipelineError(ARTIFACT_ERROR_CODES.ENVELOPE_INVALID, `Artifact ${id} HTML is empty.`);
|
|
523
|
+
}
|
|
524
|
+
if (Buffer.byteLength(html, "utf8") > MAX_ARTIFACT_HTML_BYTES) {
|
|
525
|
+
invalid(`Artifact ${id} exceeds ${MAX_ARTIFACT_HTML_BYTES} UTF-8 bytes.`);
|
|
526
|
+
}
|
|
527
|
+
const sha256 = digestArtifact(html);
|
|
528
|
+
if (artifact.sha256 !== void 0 && artifact.sha256 !== sha256) {
|
|
529
|
+
throw new PipelineError(
|
|
530
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
531
|
+
`Artifact ${id} digest does not match its canonical bundled HTML.`,
|
|
532
|
+
"",
|
|
533
|
+
{ expected: sha256, actual: artifact.sha256 }
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
const colorScheme = artifact.colorScheme ?? "light";
|
|
537
|
+
if (!["light", "dark"].includes(colorScheme)) {
|
|
538
|
+
throw new PipelineError(
|
|
539
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
540
|
+
`Artifact ${id} has an invalid color scheme.`
|
|
541
|
+
);
|
|
542
|
+
}
|
|
543
|
+
return {
|
|
544
|
+
id,
|
|
545
|
+
kind: "html",
|
|
546
|
+
title,
|
|
547
|
+
sha256,
|
|
548
|
+
html,
|
|
549
|
+
viewport: normalizeViewport(artifact.viewport),
|
|
550
|
+
colorScheme
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
function normalizeViewer(viewer, artifacts) {
|
|
554
|
+
const ids = new Set(artifacts.map(({ id }) => id));
|
|
555
|
+
const normalized = {
|
|
556
|
+
mode: viewer?.mode ?? (artifacts.length > 1 ? "variants" : "single"),
|
|
557
|
+
activeArtifactId: viewer?.activeArtifactId ?? artifacts[0].id
|
|
558
|
+
};
|
|
559
|
+
if (!["single", "variants"].includes(normalized.mode)) {
|
|
560
|
+
throw new PipelineError(
|
|
561
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
562
|
+
"Viewer mode must be single or variants."
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (!ids.has(normalized.activeArtifactId)) {
|
|
566
|
+
throw new PipelineError(
|
|
567
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
568
|
+
"Viewer activeArtifactId is not present in artifacts."
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
if (viewer?.presentation !== void 0) {
|
|
572
|
+
if (!ARTIFACT_PRESENTATIONS.includes(viewer.presentation)) {
|
|
573
|
+
throw new PipelineError(
|
|
574
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
575
|
+
"Viewer presentation must be document or canvas."
|
|
576
|
+
);
|
|
577
|
+
}
|
|
578
|
+
normalized.presentation = viewer.presentation;
|
|
579
|
+
}
|
|
580
|
+
return normalized;
|
|
581
|
+
}
|
|
582
|
+
function envelopeWithoutReview(envelope) {
|
|
583
|
+
return {
|
|
584
|
+
schemaVersion: envelope.schemaVersion,
|
|
585
|
+
artifacts: envelope.artifacts,
|
|
586
|
+
viewer: envelope.viewer
|
|
587
|
+
};
|
|
588
|
+
}
|
|
589
|
+
function canonicalEnvelopeBytes(envelope, { includeReview = false } = {}) {
|
|
590
|
+
const value = includeReview ? envelope : envelopeWithoutReview(envelope);
|
|
591
|
+
return canonicalBytes(value);
|
|
592
|
+
}
|
|
593
|
+
function digestArtifactEnvelope(envelope) {
|
|
594
|
+
return sha256Hex(canonicalEnvelopeBytes(envelope));
|
|
595
|
+
}
|
|
596
|
+
function validateArtifactReview(review) {
|
|
597
|
+
const issues = validate(review, "artifact-review", "v1.1.0");
|
|
598
|
+
if (issues.length > 0) {
|
|
599
|
+
throw new PipelineError(
|
|
600
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
601
|
+
`Invalid artifact review at ${issues[0].path}: ${issues[0].detail}`,
|
|
602
|
+
"",
|
|
603
|
+
{ issues }
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
assertBoundedString(review.reviewId, "reviewId", { min: 1, max: 128 });
|
|
607
|
+
assertBoundedString(review.reviewOf, "reviewOf", { min: 64, max: 64, pattern: SHA256_RE });
|
|
608
|
+
assertBoundedString(review.overall, "overall", { max: 65536 });
|
|
609
|
+
if (!Array.isArray(review.pins) || review.pins.length > MAX_PINS)
|
|
610
|
+
invalid(`Review pins exceed ${MAX_PINS}.`);
|
|
611
|
+
for (const [pinIndex, pin] of review.pins.entries()) {
|
|
612
|
+
const label = `pins[${pinIndex}]`;
|
|
613
|
+
assertBoundedString(pin.id, `${label}.id`, { min: 1, max: 128 });
|
|
614
|
+
assertBoundedString(pin.author?.id ?? "", `${label}.author.id`, { max: 128 });
|
|
615
|
+
assertBoundedString(pin.author?.name, `${label}.author.name`, { min: 1, max: 256 });
|
|
616
|
+
assertBoundedString(pin.artifactId, `${label}.artifactId`, { min: 1, max: 128 });
|
|
617
|
+
if (pin.variant !== void 0)
|
|
618
|
+
assertBoundedString(pin.variant, `${label}.variant`, { min: 1, max: 128 });
|
|
619
|
+
assertBoundedString(pin.comment, `${label}.comment`, { min: 1, max: 65536 });
|
|
620
|
+
if (pin.anchor) {
|
|
621
|
+
assertBoundedString(pin.anchor.planrId, `${label}.anchor.planrId`, { min: 1, max: 512 });
|
|
622
|
+
if (pin.anchor.screen !== void 0)
|
|
623
|
+
assertBoundedString(pin.anchor.screen, `${label}.anchor.screen`, { min: 1, max: 128 });
|
|
624
|
+
}
|
|
625
|
+
assertViewport(pin.viewport, `${label}.viewport`, { maxHeight: ARTIFACT_DOCUMENT_MAX_HEIGHT });
|
|
626
|
+
for (const coordinate of ["x", "y", "w", "h"]) {
|
|
627
|
+
const value = pin.region?.[coordinate];
|
|
628
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
|
|
629
|
+
invalid(`${label}.region.${coordinate} must be a finite normalized coordinate.`);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
if (pin.region.x + pin.region.w > 1 || pin.region.y + pin.region.h > 1) {
|
|
633
|
+
invalid(`${label}.region must remain inside normalized artifact bounds.`);
|
|
634
|
+
}
|
|
635
|
+
if (!Array.isArray(pin.replies) || pin.replies.length > MAX_REPLIES)
|
|
636
|
+
invalid(`${label}.replies exceeds ${MAX_REPLIES}.`);
|
|
637
|
+
for (const [replyIndex, reply] of pin.replies.entries()) {
|
|
638
|
+
const replyLabel = `${label}.replies[${replyIndex}]`;
|
|
639
|
+
assertBoundedString(reply.id, `${replyLabel}.id`, { min: 1, max: 128 });
|
|
640
|
+
assertBoundedString(reply.author?.id ?? "", `${replyLabel}.author.id`, { max: 128 });
|
|
641
|
+
assertBoundedString(reply.author?.name, `${replyLabel}.author.name`, { min: 1, max: 256 });
|
|
642
|
+
assertBoundedString(reply.comment, `${replyLabel}.comment`, { min: 1, max: 65536 });
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
return review;
|
|
646
|
+
}
|
|
647
|
+
function validateArtifactEnvelope(envelope) {
|
|
648
|
+
const issues = validate(envelope, "artifact-envelope", "v1.1.0");
|
|
649
|
+
if (issues.length > 0) {
|
|
650
|
+
throw new PipelineError(
|
|
651
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
652
|
+
`Invalid artifact envelope at ${issues[0].path}: ${issues[0].detail}`,
|
|
653
|
+
"",
|
|
654
|
+
{ issues }
|
|
655
|
+
);
|
|
656
|
+
}
|
|
657
|
+
if (!Array.isArray(envelope.artifacts) || envelope.artifacts.length < 1 || envelope.artifacts.length > MAX_ARTIFACTS) {
|
|
658
|
+
invalid(`Envelope requires 1 through ${MAX_ARTIFACTS} artifacts.`);
|
|
659
|
+
}
|
|
660
|
+
const ids = envelope.artifacts.map(({ id }) => id);
|
|
661
|
+
if (new Set(ids).size !== ids.length) {
|
|
662
|
+
throw new PipelineError(ARTIFACT_ERROR_CODES.ENVELOPE_INVALID, "Artifact ids must be unique.");
|
|
663
|
+
}
|
|
664
|
+
let artifactBytes = 0;
|
|
665
|
+
for (const artifact of envelope.artifacts) {
|
|
666
|
+
assertBoundedString(artifact.id, "artifact.id", { min: 1, max: 128, pattern: ID_RE });
|
|
667
|
+
assertBoundedString(artifact.title, `Artifact ${artifact.id} title`, { min: 1, max: 512 });
|
|
668
|
+
assertBoundedString(artifact.html, `Artifact ${artifact.id} HTML`, { min: 1 });
|
|
669
|
+
artifactBytes += Buffer.byteLength(artifact.html, "utf8");
|
|
670
|
+
if (artifactBytes > MAX_ARTIFACT_HTML_BYTES) {
|
|
671
|
+
invalid(`Envelope artifacts exceed ${MAX_ARTIFACT_HTML_BYTES} UTF-8 bytes in total.`);
|
|
672
|
+
}
|
|
673
|
+
assertViewport(artifact.viewport, `Artifact ${artifact.id} viewport`);
|
|
674
|
+
if (!SHA256_RE.test(artifact.sha256) || digestArtifact(artifact.html) !== artifact.sha256) {
|
|
675
|
+
throw new PipelineError(
|
|
676
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
677
|
+
`Artifact ${artifact.id} digest is invalid.`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
assertBoundedString(envelope.viewer.activeArtifactId, "viewer.activeArtifactId", {
|
|
682
|
+
min: 1,
|
|
683
|
+
max: 128
|
|
684
|
+
});
|
|
685
|
+
if (!ids.includes(envelope.viewer.activeArtifactId)) {
|
|
686
|
+
throw new PipelineError(
|
|
687
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
688
|
+
"Viewer references an unknown artifact."
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
if (envelope.review) {
|
|
692
|
+
validateArtifactReview(envelope.review);
|
|
693
|
+
for (const pin of envelope.review.pins) {
|
|
694
|
+
if (!ids.includes(pin.artifactId))
|
|
695
|
+
invalid(`Review pin references unknown artifact: ${pin.artifactId}`);
|
|
696
|
+
}
|
|
697
|
+
const expected = digestArtifactEnvelope(envelope);
|
|
698
|
+
if (envelope.review.reviewOf !== expected) {
|
|
699
|
+
throw new PipelineError(
|
|
700
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
701
|
+
"Review digest does not match the canonical review-free envelope.",
|
|
702
|
+
"",
|
|
703
|
+
{ expected, actual: envelope.review.reviewOf }
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
return envelope;
|
|
708
|
+
}
|
|
709
|
+
function createArtifactEnvelope({ artifacts, viewer, review } = {}) {
|
|
710
|
+
if (!Array.isArray(artifacts) || artifacts.length === 0 || artifacts.length > MAX_ARTIFACTS) {
|
|
711
|
+
throw new PipelineError(
|
|
712
|
+
ARTIFACT_ERROR_CODES.ENVELOPE_INVALID,
|
|
713
|
+
"Envelope requires 1 through 256 artifacts."
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
const normalizedArtifacts = [];
|
|
717
|
+
let artifactBytes = 0;
|
|
718
|
+
for (const artifact of artifacts) {
|
|
719
|
+
const normalized = normalizeArtifact(artifact);
|
|
720
|
+
artifactBytes += Buffer.byteLength(normalized.html, "utf8");
|
|
721
|
+
if (artifactBytes > MAX_ARTIFACT_HTML_BYTES) {
|
|
722
|
+
invalid(`Envelope artifacts exceed ${MAX_ARTIFACT_HTML_BYTES} UTF-8 bytes in total.`);
|
|
723
|
+
}
|
|
724
|
+
normalizedArtifacts.push(normalized);
|
|
725
|
+
}
|
|
726
|
+
if (new Set(normalizedArtifacts.map(({ id }) => id)).size !== normalizedArtifacts.length) {
|
|
727
|
+
throw new PipelineError(ARTIFACT_ERROR_CODES.ENVELOPE_INVALID, "Artifact ids must be unique.");
|
|
728
|
+
}
|
|
729
|
+
const envelope = {
|
|
730
|
+
schemaVersion: "1.0.0",
|
|
731
|
+
artifacts: normalizedArtifacts,
|
|
732
|
+
viewer: normalizeViewer(viewer, normalizedArtifacts),
|
|
733
|
+
...review ? { review: canonicalObject(review) } : {}
|
|
734
|
+
};
|
|
735
|
+
return validateArtifactEnvelope(envelope);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// packages/artifact/lib/artifact/internal/escape.mjs
|
|
739
|
+
var HTML_ENTITIES = Object.freeze({
|
|
740
|
+
"&": "&",
|
|
741
|
+
"<": "<",
|
|
742
|
+
">": ">",
|
|
743
|
+
'"': """,
|
|
744
|
+
"'": "'"
|
|
745
|
+
});
|
|
746
|
+
var LINE_SEP = String.fromCharCode(8232);
|
|
747
|
+
var PARA_SEP = String.fromCharCode(8233);
|
|
748
|
+
var JSON_HTML_ESCAPES = Object.freeze({
|
|
749
|
+
"<": "\\u003c",
|
|
750
|
+
">": "\\u003e",
|
|
751
|
+
"&": "\\u0026",
|
|
752
|
+
[LINE_SEP]: "\\u2028",
|
|
753
|
+
[PARA_SEP]: "\\u2029"
|
|
754
|
+
});
|
|
755
|
+
var JSON_HTML_RE = new RegExp(`[<>&${LINE_SEP}${PARA_SEP}]`, "g");
|
|
756
|
+
function escapeHtml(value) {
|
|
757
|
+
return String(value ?? "").replace(/[&<>"']/g, (ch) => HTML_ENTITIES[ch]);
|
|
758
|
+
}
|
|
759
|
+
function embedJson(value) {
|
|
760
|
+
return JSON.stringify(value ?? null).replace(JSON_HTML_RE, (ch) => JSON_HTML_ESCAPES[ch]);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
// packages/artifact/lib/artifact/ui/renderers.mjs
|
|
764
|
+
var ARTIFACT_SHELL_STATES = Object.freeze([
|
|
765
|
+
"ready",
|
|
766
|
+
"empty",
|
|
767
|
+
"bundling",
|
|
768
|
+
"loading",
|
|
769
|
+
"invalid",
|
|
770
|
+
"expired",
|
|
771
|
+
"decryption-failed",
|
|
772
|
+
"unsupported-browser"
|
|
773
|
+
]);
|
|
774
|
+
var ARTIFACT_VIEW_MODES = Object.freeze(["single", "variants", "split"]);
|
|
775
|
+
var ARTIFACT_REVIEW_MODES = Object.freeze(["interact", "comment"]);
|
|
776
|
+
var ARTIFACT_SHELL_THEMES = Object.freeze(["auto", "light", "dark"]);
|
|
777
|
+
var ARTIFACT_PRESENTATIONS2 = Object.freeze(["document", "canvas"]);
|
|
778
|
+
var STATUS_COPY = Object.freeze({
|
|
779
|
+
empty: Object.freeze({
|
|
780
|
+
title: "No artifact content",
|
|
781
|
+
detail: "Choose a bundled HTML artifact to begin this review."
|
|
782
|
+
}),
|
|
783
|
+
bundling: Object.freeze({
|
|
784
|
+
title: "Bundling artifact",
|
|
785
|
+
detail: "Packaging local scripts, styles, fonts, and images without network access."
|
|
786
|
+
}),
|
|
787
|
+
loading: Object.freeze({
|
|
788
|
+
title: "Loading private review",
|
|
789
|
+
detail: "Validating the envelope and frozen artifact viewport."
|
|
790
|
+
}),
|
|
791
|
+
invalid: Object.freeze({
|
|
792
|
+
title: "This review is invalid",
|
|
793
|
+
detail: "The artifact envelope could not be decoded or validated."
|
|
794
|
+
}),
|
|
795
|
+
expired: Object.freeze({
|
|
796
|
+
title: "This encrypted review expired",
|
|
797
|
+
detail: "Ask the sender for a new immutable review link."
|
|
798
|
+
}),
|
|
799
|
+
"decryption-failed": Object.freeze({
|
|
800
|
+
title: "This key cannot decrypt the review",
|
|
801
|
+
detail: "Use the complete link, including its private fragment key."
|
|
802
|
+
}),
|
|
803
|
+
"unsupported-browser": Object.freeze({
|
|
804
|
+
title: "Browser support is required",
|
|
805
|
+
detail: "Use a current browser with raw DEFLATE and Web Crypto support."
|
|
806
|
+
})
|
|
807
|
+
});
|
|
808
|
+
var PRIVACY_LABELS = Object.freeze({
|
|
809
|
+
local: "Local review",
|
|
810
|
+
fragment: "Private fragment",
|
|
811
|
+
"encrypted-short": "Encrypted short link"
|
|
812
|
+
});
|
|
813
|
+
function plainText(value, fallback = "") {
|
|
814
|
+
if (typeof value === "string") return value;
|
|
815
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
816
|
+
return fallback;
|
|
817
|
+
}
|
|
818
|
+
function member(value, allowed, fallback) {
|
|
819
|
+
return allowed.includes(value) ? value : fallback;
|
|
820
|
+
}
|
|
821
|
+
function nonNegativeInteger(value, fallback = 0) {
|
|
822
|
+
return Number.isInteger(value) && value >= 0 ? value : fallback;
|
|
823
|
+
}
|
|
824
|
+
function viewportDimension(value, fallback) {
|
|
825
|
+
return Number.isInteger(value) && value > 0 && value <= 16384 ? value : fallback;
|
|
826
|
+
}
|
|
827
|
+
function resolveArtifactPresentation(value, { mode = "single", artifactCount = 1 } = {}) {
|
|
828
|
+
if (ARTIFACT_PRESENTATIONS2.includes(value)) return value;
|
|
829
|
+
return artifactCount > 1 || mode === "variants" || mode === "split" ? "canvas" : "document";
|
|
830
|
+
}
|
|
831
|
+
function normalizeArtifact2(value, index) {
|
|
832
|
+
const artifact = value && typeof value === "object" ? value : {};
|
|
833
|
+
const id = plainText(artifact.id, `artifact-${index + 1}`) || `artifact-${index + 1}`;
|
|
834
|
+
return Object.freeze({
|
|
835
|
+
id,
|
|
836
|
+
domId: `planr-artifact-${index + 1}`,
|
|
837
|
+
title: plainText(artifact.title, `Artifact ${index + 1}`) || `Artifact ${index + 1}`,
|
|
838
|
+
kind: plainText(artifact.kind, "html") || "html",
|
|
839
|
+
viewport: Object.freeze({
|
|
840
|
+
width: viewportDimension(artifact.viewport?.width, 1440),
|
|
841
|
+
height: viewportDimension(artifact.viewport?.height, 900)
|
|
842
|
+
}),
|
|
843
|
+
colorScheme: member(artifact.colorScheme, ["light", "dark"], "light")
|
|
844
|
+
});
|
|
845
|
+
}
|
|
846
|
+
function normalizeArtifactShellModel(input = {}) {
|
|
847
|
+
const source = input && typeof input === "object" ? input : {};
|
|
848
|
+
const envelope = source.envelope && typeof source.envelope === "object" ? source.envelope : {};
|
|
849
|
+
const viewer = source.viewer && typeof source.viewer === "object" ? source.viewer : envelope.viewer && typeof envelope.viewer === "object" ? envelope.viewer : {};
|
|
850
|
+
const shell = source.shell && typeof source.shell === "object" ? source.shell : {};
|
|
851
|
+
const artifacts = Object.freeze(
|
|
852
|
+
(Array.isArray(envelope.artifacts) ? envelope.artifacts : []).map(normalizeArtifact2)
|
|
853
|
+
);
|
|
854
|
+
const requestedActiveId = plainText(viewer.activeArtifactId);
|
|
855
|
+
const activeIndex = Math.max(
|
|
856
|
+
0,
|
|
857
|
+
artifacts.findIndex((artifact) => artifact.id === requestedActiveId)
|
|
858
|
+
);
|
|
859
|
+
const activeArtifact = artifacts[activeIndex] ?? null;
|
|
860
|
+
const requestedComparisonId = plainText(viewer.comparisonArtifactId);
|
|
861
|
+
let comparisonIndex = artifacts.findIndex(
|
|
862
|
+
(artifact, index) => index !== activeIndex && artifact.id === requestedComparisonId
|
|
863
|
+
);
|
|
864
|
+
if (comparisonIndex < 0 && artifacts.length > 1) comparisonIndex = activeIndex === 0 ? 1 : 0;
|
|
865
|
+
const comparisonArtifact = comparisonIndex >= 0 ? artifacts[comparisonIndex] : null;
|
|
866
|
+
let viewMode = member(
|
|
867
|
+
viewer.mode,
|
|
868
|
+
ARTIFACT_VIEW_MODES,
|
|
869
|
+
artifacts.length > 1 ? "variants" : "single"
|
|
870
|
+
);
|
|
871
|
+
if (artifacts.length < 2 && viewMode !== "single") viewMode = "single";
|
|
872
|
+
const reviewMode = member(
|
|
873
|
+
viewer.reviewMode ?? shell.reviewMode,
|
|
874
|
+
ARTIFACT_REVIEW_MODES,
|
|
875
|
+
"interact"
|
|
876
|
+
);
|
|
877
|
+
let status = member(viewer.status ?? shell.status, ARTIFACT_SHELL_STATES, "ready");
|
|
878
|
+
if (artifacts.length === 0 && status === "ready") status = "empty";
|
|
879
|
+
const privacy = member(shell.privacy, Object.keys(PRIVACY_LABELS), "local");
|
|
880
|
+
const theme = member(shell.theme, ARTIFACT_SHELL_THEMES, "auto");
|
|
881
|
+
const zoom = Math.min(200, Math.max(25, nonNegativeInteger(shell.zoom, 72)));
|
|
882
|
+
const feedbackCount = nonNegativeInteger(shell.feedbackCount, 0);
|
|
883
|
+
const presentation = resolveArtifactPresentation(viewer.presentation ?? shell.presentation, {
|
|
884
|
+
mode: viewMode,
|
|
885
|
+
artifactCount: artifacts.length
|
|
886
|
+
});
|
|
887
|
+
return Object.freeze({
|
|
888
|
+
schemaVersion: "1.0.0",
|
|
889
|
+
title: plainText(shell.title, activeArtifact?.title ?? "Artifact review") || "Artifact review",
|
|
890
|
+
artifacts,
|
|
891
|
+
activeArtifact,
|
|
892
|
+
activeIndex,
|
|
893
|
+
comparisonArtifact,
|
|
894
|
+
comparisonIndex,
|
|
895
|
+
viewMode,
|
|
896
|
+
reviewMode,
|
|
897
|
+
status,
|
|
898
|
+
statusCopy: STATUS_COPY[status] ?? null,
|
|
899
|
+
privacy,
|
|
900
|
+
privacyLabel: PRIVACY_LABELS[privacy],
|
|
901
|
+
theme,
|
|
902
|
+
zoom,
|
|
903
|
+
feedbackCount,
|
|
904
|
+
presentation,
|
|
905
|
+
railOpen: shell.railOpen === void 0 ? presentation === "canvas" : Boolean(shell.railOpen)
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
function activeMetadata(model) {
|
|
909
|
+
const artifact = model.activeArtifact;
|
|
910
|
+
if (!artifact) return "HTML \xB7 1440\xD7900";
|
|
911
|
+
return `${artifact.kind.toUpperCase()} \xB7 ${artifact.viewport.width}\xD7${artifact.viewport.height}`;
|
|
912
|
+
}
|
|
913
|
+
function renderPlanrMark() {
|
|
914
|
+
return '<span class="planr-mark" aria-hidden="true"><svg viewBox="0 0 160 160" focusable="false"><g transform="rotate(-45 80 80)"><path d="M125 50A52 52 0 1 0 125 110" fill="none" stroke="currentColor" stroke-width="12" stroke-linecap="round" stroke-linejoin="round"/><rect x="127" y="71" width="18" height="18" rx="3" fill="currentColor"/></g></svg></span>';
|
|
915
|
+
}
|
|
916
|
+
function renderArtifactToolbar(model) {
|
|
917
|
+
const interact = model.reviewMode === "interact";
|
|
918
|
+
const canvas = model.presentation === "canvas";
|
|
919
|
+
return `<header class="planr-toolbar">
|
|
920
|
+
<div class="planr-brand" aria-label="OpenPlanr">${renderPlanrMark()}<span class="planr-title-block"><strong>${escapeHtml(model.title)}</strong>${canvas ? `<span>${escapeHtml(activeMetadata(model))}</span>` : ""}</span></div>
|
|
921
|
+
<span class="planr-privacy" data-privacy="${escapeHtml(model.privacy)}">${escapeHtml(model.privacyLabel)}</span>
|
|
922
|
+
${canvas ? ' <span class="planr-domain-toolbar" data-planr-slot="domain-toolbar" aria-label="Artifact workflow controls"></span>\n' : ""} <span class="planr-toolbar-spacer" aria-hidden="true"></span>
|
|
923
|
+
${canvas ? ` <div class="planr-segment" role="group" aria-label="Viewport controls"><button type="button" data-planr-action="zoom-out" aria-label="Zoom out">\u2212</button><button type="button" data-planr-action="zoom-reset" aria-label="Reset zoom">${model.zoom}%</button><button type="button" data-planr-action="zoom-in" aria-label="Zoom in">+</button></div>
|
|
924
|
+
` : ""} <div class="planr-segment" role="group" aria-label="Review mode"><button type="button" data-planr-mode="interact" data-planr-short-label="I" aria-pressed="${interact}">Interact</button><button type="button" data-planr-mode="comment" data-planr-short-label="C" aria-pressed="${!interact}">Comment</button></div>
|
|
925
|
+
${canvas ? ` <button class="planr-toolbar-action" type="button" data-planr-action="theme" aria-label="Shell theme ${escapeHtml(model.theme)}">${escapeHtml(model.theme)}</button>
|
|
926
|
+
` : ""} <button class="planr-toolbar-action" type="button" data-planr-action="feedback" aria-controls="planr-review-rail" aria-expanded="${model.railOpen}"><span class="planr-feedback-label">Feedback</span> <span class="planr-count">${model.feedbackCount}</span></button>
|
|
927
|
+
<button class="planr-toolbar-action planr-share" type="button" data-planr-action="share" aria-haspopup="dialog">Share</button>
|
|
928
|
+
</header>`;
|
|
929
|
+
}
|
|
930
|
+
function actionIcon(name) {
|
|
931
|
+
const paths = {
|
|
932
|
+
comment: '<path d="M12 20a8 8 0 1 0-7.1-4.3L4 20l4.3-.9A8 8 0 0 0 12 20Z"/><path d="M12 8v8M8 12h8"/>',
|
|
933
|
+
comments: '<path d="M20 11.5a7.5 7.5 0 0 1-8 7.5 8.8 8.8 0 0 1-3.2-.6L4 20l1.6-4.1A7.4 7.4 0 0 1 4 11.5a8 8 0 0 1 16 0Z"/><path d="M8.5 11.5h7"/>',
|
|
934
|
+
share: '<path d="M12 3v12M7 8l5-5 5 5"/><path d="M5 13v6h14v-6"/>',
|
|
935
|
+
more: '<circle cx="5" cy="12" r="1.25"/><circle cx="12" cy="12" r="1.25"/><circle cx="19" cy="12" r="1.25"/>'
|
|
936
|
+
};
|
|
937
|
+
return `<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">${paths[name]}</svg>`;
|
|
938
|
+
}
|
|
939
|
+
function commentsLabel(count) {
|
|
940
|
+
return `${count} ${count === 1 ? "comment" : "comments"}`;
|
|
941
|
+
}
|
|
942
|
+
function renderDocumentActionRail(model) {
|
|
943
|
+
return `<nav class="planr-floating-actions" aria-label="Review actions">
|
|
944
|
+
<button type="button" data-planr-action="add-comment" aria-label="Add comment" aria-keyshortcuts="C" aria-pressed="${model.reviewMode === "comment"}" data-planr-tooltip="Add comment (C)">${actionIcon("comment")}</button>
|
|
945
|
+
<button type="button" data-planr-action="feedback" aria-label="${commentsLabel(model.feedbackCount)}" aria-controls="planr-review-rail" aria-expanded="${model.railOpen}" data-planr-tooltip="Comments">${actionIcon("comments")}<span class="planr-count" aria-hidden="true">${model.feedbackCount}</span></button>
|
|
946
|
+
<button type="button" data-planr-action="share" aria-label="Share review" aria-haspopup="dialog" data-planr-tooltip="Share">${actionIcon("share")}<span class="planr-action-label">Share</span></button>
|
|
947
|
+
<div class="planr-more">
|
|
948
|
+
<button type="button" data-planr-action="more" aria-label="More review options" aria-haspopup="menu" aria-expanded="false" data-planr-tooltip="More">${actionIcon("more")}</button>
|
|
949
|
+
<div class="planr-more-menu" role="menu" hidden>
|
|
950
|
+
<div><strong>Private review</strong><span>${escapeHtml(model.privacyLabel)} \xB7 Encrypted when shared</span></div>
|
|
951
|
+
<button type="button" role="menuitem" data-planr-action="sharing-options">Snapshot and sharing options</button>
|
|
952
|
+
<div data-planr-slot="room-manager" hidden></div>
|
|
953
|
+
</div>
|
|
954
|
+
</div>
|
|
955
|
+
</nav>`;
|
|
956
|
+
}
|
|
957
|
+
function renderDocumentCommentsScrim() {
|
|
958
|
+
return '<button class="planr-comments-scrim" type="button" data-planr-comments-scrim data-planr-close-feedback aria-label="Close comments" tabindex="-1"></button>';
|
|
959
|
+
}
|
|
960
|
+
function renderViewButton(mode, model, disabled = false) {
|
|
961
|
+
const label = mode[0].toUpperCase() + mode.slice(1);
|
|
962
|
+
return `<button type="button" data-planr-view="${mode}" aria-pressed="${model.viewMode === mode}"${disabled ? " disabled" : ""}>${label}</button>`;
|
|
963
|
+
}
|
|
964
|
+
function renderArtifactVariantControls(model) {
|
|
965
|
+
const hasVariants = model.artifacts.length > 1;
|
|
966
|
+
const tabs = model.artifacts.map((artifact, index) => {
|
|
967
|
+
const selected = index === model.activeIndex;
|
|
968
|
+
return `<button type="button" role="tab" id="planr-variant-tab-${index + 1}" aria-controls="${artifact.domId}-panel" aria-selected="${selected}" tabindex="${selected ? 0 : -1}" data-artifact-id="${escapeHtml(artifact.id)}"><span>${String(index + 1).padStart(2, "0")}</span> ${escapeHtml(artifact.title)}</button>`;
|
|
969
|
+
}).join("");
|
|
970
|
+
return `<div class="planr-stage-controls" role="group" aria-label="Artifact display controls">
|
|
971
|
+
<div class="planr-segment planr-view-modes" role="group" aria-label="Artifact view mode">${renderViewButton("single", model)}${renderViewButton("variants", model, !hasVariants)}${renderViewButton("split", model, !hasVariants)}</div>
|
|
972
|
+
<div class="planr-variants" role="tablist" aria-label="Artifact variants"${model.viewMode === "single" || !hasVariants ? " hidden" : ""}>${tabs}</div>
|
|
973
|
+
</div>`;
|
|
974
|
+
}
|
|
975
|
+
function visibleArtifactIndexes(model) {
|
|
976
|
+
if (model.viewMode === "split" && model.comparisonArtifact) {
|
|
977
|
+
return /* @__PURE__ */ new Set([model.activeIndex, model.comparisonIndex]);
|
|
978
|
+
}
|
|
979
|
+
return new Set(model.activeArtifact ? [model.activeIndex] : []);
|
|
980
|
+
}
|
|
981
|
+
function renderArtifactPanels(model) {
|
|
982
|
+
const visible = visibleArtifactIndexes(model);
|
|
983
|
+
const unavailable = model.status !== "ready";
|
|
984
|
+
return model.artifacts.map((artifact, index) => {
|
|
985
|
+
const hidden = !visible.has(index);
|
|
986
|
+
const primary = index === model.activeIndex;
|
|
987
|
+
const annotationTabIndex = !hidden && !unavailable && model.reviewMode === "comment" ? 0 : -1;
|
|
988
|
+
const frameTabIndex = !hidden && !unavailable && model.reviewMode === "interact" ? 0 : -1;
|
|
989
|
+
return `<section class="planr-artifact-panel" id="${artifact.domId}-panel" role="tabpanel" aria-labelledby="planr-variant-tab-${index + 1}" data-artifact-id="${escapeHtml(artifact.id)}" data-artifact-color-scheme="${artifact.colorScheme}" style="--planr-artifact-width:${artifact.viewport.width}px;--planr-artifact-height:${artifact.viewport.height}px" aria-label="${primary ? "Primary" : "Comparison"} artifact: ${escapeHtml(artifact.title)}"${hidden ? " hidden" : ""}>
|
|
990
|
+
<span class="planr-frame-label">${String(index + 1).padStart(2, "0")} \xB7 ${escapeHtml(artifact.title)}</span>
|
|
991
|
+
<div class="planr-frame"><iframe id="${artifact.domId}" title="${escapeHtml(artifact.title)} artifact" sandbox="allow-scripts" referrerpolicy="no-referrer" tabindex="${frameTabIndex}" data-planr-artifact-frame="${escapeHtml(artifact.id)}"></iframe></div>
|
|
992
|
+
<div class="planr-annotation-layer" role="region" aria-label="Annotations for ${escapeHtml(artifact.title)}" aria-disabled="${annotationTabIndex < 0}" tabindex="${annotationTabIndex}" data-coordinate-space="normalized" data-planr-annotation-layer="${escapeHtml(artifact.id)}"></div>
|
|
993
|
+
</section>`;
|
|
994
|
+
}).join("");
|
|
995
|
+
}
|
|
996
|
+
function renderArtifactStatus(model) {
|
|
997
|
+
const ready = model.status === "ready";
|
|
998
|
+
const copy = model.statusCopy ?? { title: "", detail: "" };
|
|
999
|
+
return `<div class="planr-stage-status" role="status" aria-live="polite" aria-atomic="true"${ready ? " hidden" : ""}>
|
|
1000
|
+
<div><strong>${escapeHtml(copy.title)}</strong><p>${escapeHtml(copy.detail)}</p></div>
|
|
1001
|
+
</div>`;
|
|
1002
|
+
}
|
|
1003
|
+
function renderArtifactStage(model) {
|
|
1004
|
+
const unavailable = model.status !== "ready";
|
|
1005
|
+
const breadcrumb = model.activeArtifact?.title ?? "Artifact";
|
|
1006
|
+
return `<main class="planr-stage" aria-label="Artifact review stage">
|
|
1007
|
+
${model.presentation === "canvas" ? ` <div class="planr-stage-heading"><span>ARTIFACT / ${escapeHtml(breadcrumb.toUpperCase())}</span><span role="status" aria-live="polite" data-planr-slot="status">${model.reviewMode === "comment" ? "Comment mode" : "Interactions enabled"}</span></div>
|
|
1008
|
+
${renderArtifactVariantControls(model)}
|
|
1009
|
+
` : ""} <div class="planr-stage-scroll"><div class="planr-stage-surface" style="--planr-shell-zoom:${model.zoom / 100}"${unavailable ? ' inert aria-hidden="true"' : ""}><div class="planr-frame-grid" data-planr-layout="${model.viewMode}">${renderArtifactPanels(model)}</div></div></div>
|
|
1010
|
+
${renderArtifactStatus(model)}
|
|
1011
|
+
</main>`;
|
|
1012
|
+
}
|
|
1013
|
+
function renderArtifactRail(model) {
|
|
1014
|
+
const closed = !model.railOpen;
|
|
1015
|
+
return `<aside class="planr-review-rail" id="planr-review-rail" aria-label="Review comments"${closed ? ' inert aria-hidden="true"' : ""}>
|
|
1016
|
+
<header><h2>Review comments</h2><div class="planr-review-header-actions"><div class="planr-review-metrics" aria-label="Review metrics"><span data-planr-metric="open">0 open</span><span class="planr-count" data-planr-metric="total" aria-label="${commentsLabel(model.feedbackCount)}">${model.feedbackCount}</span></div><button class="planr-rail-close" type="button" data-planr-close-feedback aria-label="Close comments">\xD7</button></div></header>
|
|
1017
|
+
<section class="planr-identity" aria-label="Reviewer identity"><label for="planr-reviewer-name">Your name<input id="planr-reviewer-name" type="text" maxlength="256" autocomplete="name" aria-describedby="planr-identity-status planr-review-error" data-planr-reviewer-name></label><p class="planr-identity-status" id="planr-identity-status" aria-live="polite" data-planr-identity-status>Used to sign your comments.</p><p class="planr-field-error" id="planr-review-error" role="alert" hidden></p></section>
|
|
1018
|
+
<div class="planr-feedback-slot" data-planr-slot="feedback-rail" role="region" aria-label="Comment threads"><p data-planr-empty-feedback>No comments yet. Choose Add comment, then click or drag on the artifact.</p><div class="planr-thread-list" data-planr-thread-list></div></div>
|
|
1019
|
+
<section class="planr-domain-rail" data-planr-slot="domain-rail" aria-label="Artifact workflow details" hidden></section>
|
|
1020
|
+
<section class="planr-decision-slot" data-planr-slot="decision" aria-label="Review decision">
|
|
1021
|
+
<label for="planr-overall-note">Overall note</label><textarea id="planr-overall-note" maxlength="65536" placeholder="Overall note for the coding agent\u2026" data-planr-overall></textarea>
|
|
1022
|
+
<div><button type="button" data-planr-decision="approved" aria-pressed="false">Approve</button><button type="button" data-planr-decision="changes_requested" aria-pressed="false">Request changes</button></div>
|
|
1023
|
+
<p data-planr-slot="decision-status">Decision pending</p>
|
|
1024
|
+
</section>
|
|
1025
|
+
</aside>`;
|
|
1026
|
+
}
|
|
1027
|
+
function renderArtifactShareDialog() {
|
|
1028
|
+
return `<div class="planr-dialog-backdrop" data-planr-share-dialog hidden>
|
|
1029
|
+
<section class="planr-share-dialog" role="dialog" aria-modal="true" aria-labelledby="planr-share-title" aria-describedby="planr-share-description" data-planr-share-phase="idle" data-planr-share-selected="live">
|
|
1030
|
+
<header><div><h2 id="planr-share-title">Share this review</h2><p id="planr-share-description">Live review is encrypted and collaborative. Snapshot links are immutable alternatives.</p></div><button type="button" class="planr-dialog-close" data-planr-share-close aria-label="Close share dialog">\xD7</button></header>
|
|
1031
|
+
<div class="planr-share-receipt" role="group" aria-label="Privacy receipt">
|
|
1032
|
+
<button type="button" class="planr-share-receipt-row is-selected" data-planr-share-transport="live" aria-pressed="true">
|
|
1033
|
+
<span class="planr-share-receipt-icon" aria-hidden="true">\u25CF</span><span><strong>Live collaborative review</strong><small>Encrypted artifact and comment events are stored until expiry. Anyone with the link can comment.</small></span><span class="planr-share-receipt-size">Default</span>
|
|
1034
|
+
</button>
|
|
1035
|
+
<button type="button" class="planr-share-receipt-row" data-planr-share-transport="fragment" aria-pressed="false">
|
|
1036
|
+
<span class="planr-share-receipt-icon" aria-hidden="true">#</span><span><strong>Private fragment</strong><small>Compressed into the URL. Nothing is uploaded.</small></span><span class="planr-share-receipt-size" data-planr-share-fragment-size>Calculating\u2026</span>
|
|
1037
|
+
</button>
|
|
1038
|
+
<button type="button" class="planr-share-receipt-row" data-planr-share-transport="short" aria-pressed="false">
|
|
1039
|
+
<span class="planr-share-receipt-icon" aria-hidden="true">\u25C7</span><span><strong>Encrypted short link</strong><small>AES-256-GCM ciphertext is stored until expiry; the key stays in this link fragment.</small></span><span class="planr-share-receipt-size" data-planr-share-short-size>Calculating\u2026</span>
|
|
1040
|
+
</button>
|
|
1041
|
+
</div>
|
|
1042
|
+
<p class="planr-share-threshold" data-planr-share-threshold>Live rooms are the default. Private fragments are immutable snapshots through 8,000 characters.</p>
|
|
1043
|
+
<div class="planr-share-ttl" data-planr-share-ttl-row hidden><label for="planr-share-ttl">Encrypted storage expiry<select id="planr-share-ttl" data-planr-share-ttl><option value="1d">1 day</option><option value="7d" selected>7 days</option><option value="30d">30 days</option></select></label><p>Ciphertext and request metadata are visible to the service until <time data-planr-share-expiry></time>. The decryption key is not uploaded.</p></div>
|
|
1044
|
+
<aside class="planr-owner-custody" data-planr-share-owner-custody><strong>Owner-verdict custody</strong><p>Download the private owner key first. The key stays in your downloaded file and is never put in a URL, browser storage, log, or server request.</p><p role="status" data-planr-share-owner-custody-status>No room will be created until the private owner key is downloaded successfully.</p></aside>
|
|
1045
|
+
<section class="planr-share-result" data-planr-share-result hidden aria-label="Share receipt">
|
|
1046
|
+
<label for="planr-share-url">Reviewer URL \xB7 view and comment</label><div><input id="planr-share-url" data-planr-share-url readonly><button type="button" data-planr-share-copy-url>Copy URL</button></div>
|
|
1047
|
+
<aside class="planr-live-capability" data-planr-share-owner hidden><strong>Private owner-verdict URL</strong><p>Use this URL with the matching downloaded owner key to approve or request changes.</p><div><input id="planr-share-owner-url" aria-label="Private owner-verdict URL" data-planr-share-owner-url readonly><button type="button" data-planr-share-copy-owner>Copy owner URL</button></div></aside>
|
|
1048
|
+
<aside class="planr-live-capability" data-planr-share-manage hidden><strong>Private management URL</strong><p>Pause or reopen comments, or delete the room. Management cannot set a verdict.</p><div><input id="planr-share-manage-url" aria-label="Private management URL" data-planr-share-manage-url readonly><button type="button" data-planr-share-copy-manage>Copy manage URL</button></div></aside>
|
|
1049
|
+
<aside class="planr-deletion-receipt" data-planr-share-deletion hidden><strong>One-time deletion token</strong><p>Store this token now; it cannot be recovered. It is separate from the review URL.</p><code data-planr-share-deletion-token></code><button type="button" data-planr-share-copy-deletion>Copy deletion token</button></aside>
|
|
1050
|
+
</section>
|
|
1051
|
+
<p class="planr-share-error" role="alert" data-planr-share-error hidden></p>
|
|
1052
|
+
<p class="planr-visually-hidden" role="status" aria-live="polite" aria-atomic="true" data-planr-share-status></p>
|
|
1053
|
+
<footer><button type="button" data-planr-share-cancel>Cancel</button><button type="button" class="planr-share-confirm" data-planr-share-confirm disabled>Download owner key</button></footer>
|
|
1054
|
+
</section>
|
|
1055
|
+
</div>`;
|
|
1056
|
+
}
|
|
1057
|
+
function renderHostedArtifactViewerSlot() {
|
|
1058
|
+
return `<section class="planr-hosted-viewer" data-planr-hosted-viewer data-planr-hosted-state="idle" role="status" aria-live="polite" aria-atomic="true" hidden>
|
|
1059
|
+
<div>${renderPlanrMark()}<strong data-planr-hosted-title></strong><p data-planr-hosted-detail></p><button type="button" data-planr-hosted-retry hidden></button></div>
|
|
1060
|
+
</section>`;
|
|
1061
|
+
}
|
|
1062
|
+
function renderArtifactShellMarkup(model) {
|
|
1063
|
+
return `<div class="planr-shell" data-planr-presentation="${model.presentation}" data-planr-view="${model.viewMode}" data-planr-review-mode="${model.reviewMode}" data-planr-state="${model.status}" data-planr-rail-open="${model.railOpen}">
|
|
1064
|
+
${model.presentation === "canvas" ? renderArtifactToolbar(model) : `${renderDocumentActionRail(model)}
|
|
1065
|
+
${renderDocumentCommentsScrim()}`}
|
|
1066
|
+
<div class="planr-workspace">${renderArtifactStage(model)}${renderArtifactRail(model)}</div>
|
|
1067
|
+
</div>
|
|
1068
|
+
<div data-planr-slot="dialogs">${renderArtifactShareDialog()}</div>
|
|
1069
|
+
${renderHostedArtifactViewerSlot()}
|
|
1070
|
+
<div class="planr-visually-hidden" role="status" aria-live="polite" aria-atomic="true" data-planr-slot="review-announcer"></div>`;
|
|
1071
|
+
}
|
|
1072
|
+
function renderArtifactShellModelData(model) {
|
|
1073
|
+
return embedJson(model);
|
|
1074
|
+
}
|
|
1075
|
+
|
|
1076
|
+
// packages/artifact/lib/artifact/ui/stage-payload.mjs
|
|
1077
|
+
var PRESENTATIONS = Object.freeze(["document", "canvas"]);
|
|
1078
|
+
function positiveInteger(value, fallback) {
|
|
1079
|
+
return Number.isInteger(value) && value > 0 ? value : fallback;
|
|
1080
|
+
}
|
|
1081
|
+
function freezeArtifactMetadata(artifact, index) {
|
|
1082
|
+
if (!artifact || typeof artifact !== "object") {
|
|
1083
|
+
throw new TypeError(`Artifact ${index + 1} must be an object.`);
|
|
1084
|
+
}
|
|
1085
|
+
if (typeof artifact.id !== "string" || artifact.id.length === 0) {
|
|
1086
|
+
throw new TypeError(`Artifact ${index + 1} requires an id.`);
|
|
1087
|
+
}
|
|
1088
|
+
return Object.freeze({
|
|
1089
|
+
id: artifact.id,
|
|
1090
|
+
title: typeof artifact.title === "string" && artifact.title.length > 0 ? artifact.title : `Artifact ${index + 1}`,
|
|
1091
|
+
sha256: typeof artifact.sha256 === "string" ? artifact.sha256 : "",
|
|
1092
|
+
viewport: Object.freeze({
|
|
1093
|
+
width: positiveInteger(artifact.viewport?.width, 1440),
|
|
1094
|
+
height: positiveInteger(artifact.viewport?.height, 900)
|
|
1095
|
+
}),
|
|
1096
|
+
colorScheme: ["light", "dark"].includes(artifact.colorScheme) ? artifact.colorScheme : "light"
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
function requestedArtifactId(value) {
|
|
1100
|
+
if (typeof value === "string") return value;
|
|
1101
|
+
return typeof value?.id === "string" ? value.id : "";
|
|
1102
|
+
}
|
|
1103
|
+
function availableId(artifacts, requested, fallback = "") {
|
|
1104
|
+
return artifacts.some(({ id }) => id === requested) ? requested : fallback;
|
|
1105
|
+
}
|
|
1106
|
+
function normalizeViewMode(value, artifactCount) {
|
|
1107
|
+
if (artifactCount < 2) return "single";
|
|
1108
|
+
return ["single", "variants", "split"].includes(value) ? value : "variants";
|
|
1109
|
+
}
|
|
1110
|
+
function createArtifactStagePayload(envelope = {}, { viewer } = {}) {
|
|
1111
|
+
const artifacts = Object.freeze(
|
|
1112
|
+
(Array.isArray(envelope?.artifacts) ? envelope.artifacts : []).map(freezeArtifactMetadata)
|
|
1113
|
+
);
|
|
1114
|
+
const sourceViewer = viewer && typeof viewer === "object" ? viewer : envelope?.viewer && typeof envelope.viewer === "object" ? envelope.viewer : {};
|
|
1115
|
+
const firstId = artifacts[0]?.id ?? "";
|
|
1116
|
+
const activeArtifactId = availableId(
|
|
1117
|
+
artifacts,
|
|
1118
|
+
requestedArtifactId(sourceViewer.activeArtifactId),
|
|
1119
|
+
firstId
|
|
1120
|
+
);
|
|
1121
|
+
const mode = normalizeViewMode(sourceViewer.mode, artifacts.length);
|
|
1122
|
+
return Object.freeze({
|
|
1123
|
+
schemaVersion: "1.0.0",
|
|
1124
|
+
artifacts,
|
|
1125
|
+
viewer: Object.freeze({
|
|
1126
|
+
mode,
|
|
1127
|
+
activeArtifactId,
|
|
1128
|
+
...PRESENTATIONS.includes(sourceViewer.presentation) ? { presentation: sourceViewer.presentation } : {}
|
|
1129
|
+
})
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
|
|
1133
|
+
// packages/artifact/lib/artifact/ui/tokens.mjs
|
|
1134
|
+
import { fileURLToPath as __planrAssetFile2 } from "node:url";
|
|
1135
|
+
import { readFileSync as readFileSync2 } from "node:fs";
|
|
1136
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
1137
|
+
|
|
1138
|
+
// packages/artifact/lib/artifact/internal/contrast.mjs
|
|
1139
|
+
var clamp01 = (x) => x < 0 ? 0 : x > 1 ? 1 : x;
|
|
1140
|
+
var srgbToLinear = (c) => c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
|
1141
|
+
var linearToSrgb = (c) => {
|
|
1142
|
+
c = clamp01(c);
|
|
1143
|
+
return c <= 31308e-7 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
|
1144
|
+
};
|
|
1145
|
+
function parseHex(s) {
|
|
1146
|
+
let h = s.replace("#", "").trim();
|
|
1147
|
+
if (h.length === 3 || h.length === 4)
|
|
1148
|
+
h = h.split("").map((d) => d + d).join("");
|
|
1149
|
+
if (h.length !== 6 && h.length !== 8) return null;
|
|
1150
|
+
const r = parseInt(h.slice(0, 2), 16) / 255;
|
|
1151
|
+
const g = parseInt(h.slice(2, 4), 16) / 255;
|
|
1152
|
+
const b = parseInt(h.slice(4, 6), 16) / 255;
|
|
1153
|
+
return [r, g, b].some(Number.isNaN) ? null : { r, g, b };
|
|
1154
|
+
}
|
|
1155
|
+
function parseRgb(s) {
|
|
1156
|
+
const m = /rgba?\(([^)]+)\)/i.exec(s);
|
|
1157
|
+
if (!m) return null;
|
|
1158
|
+
const parts = m[1].split(/[,\s/]+/).filter(Boolean).slice(0, 3);
|
|
1159
|
+
if (parts.length < 3) return null;
|
|
1160
|
+
const toUnit = (p) => p.endsWith("%") ? parseFloat(p) / 100 : parseFloat(p) / 255;
|
|
1161
|
+
const [r, g, b] = parts.map(toUnit);
|
|
1162
|
+
return [r, g, b].some((x) => !Number.isFinite(x)) ? null : { r: clamp01(r), g: clamp01(g), b: clamp01(b) };
|
|
1163
|
+
}
|
|
1164
|
+
function parseOklch(s) {
|
|
1165
|
+
const m = /oklch\(([^)]+)\)/i.exec(s);
|
|
1166
|
+
if (!m) return null;
|
|
1167
|
+
const parts = m[1].split(/[\s/]+/).filter(Boolean);
|
|
1168
|
+
if (parts.length < 3) return null;
|
|
1169
|
+
let [L, C, H] = parts;
|
|
1170
|
+
L = L.endsWith("%") ? parseFloat(L) / 100 : parseFloat(L);
|
|
1171
|
+
C = parseFloat(C);
|
|
1172
|
+
H = parseFloat(H);
|
|
1173
|
+
if (![L, C, H].every(Number.isFinite)) return null;
|
|
1174
|
+
const h = H * Math.PI / 180;
|
|
1175
|
+
const a = C * Math.cos(h);
|
|
1176
|
+
const b2 = C * Math.sin(h);
|
|
1177
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b2;
|
|
1178
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b2;
|
|
1179
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b2;
|
|
1180
|
+
const l = l_ ** 3, mm = m_ ** 3, ss = s_ ** 3;
|
|
1181
|
+
return {
|
|
1182
|
+
r: linearToSrgb(4.0767416621 * l - 3.3077115913 * mm + 0.2309699292 * ss),
|
|
1183
|
+
g: linearToSrgb(-1.2684380046 * l + 2.6097574011 * mm - 0.3413193965 * ss),
|
|
1184
|
+
b: linearToSrgb(-0.0041960863 * l - 0.7034186147 * mm + 1.707614701 * ss)
|
|
1185
|
+
};
|
|
1186
|
+
}
|
|
1187
|
+
var NAMED = { white: { r: 1, g: 1, b: 1 }, black: { r: 0, g: 0, b: 0 } };
|
|
1188
|
+
function parseColor(input) {
|
|
1189
|
+
if (!input || typeof input !== "string") return null;
|
|
1190
|
+
const s = input.trim().toLowerCase();
|
|
1191
|
+
if (s.includes("var(") || s.includes("gradient") || ["currentcolor", "transparent", "inherit", "none"].includes(s))
|
|
1192
|
+
return null;
|
|
1193
|
+
if (s in NAMED) return NAMED[s];
|
|
1194
|
+
if (s.startsWith("#")) return parseHex(s);
|
|
1195
|
+
if (s.startsWith("rgb")) return parseRgb(s);
|
|
1196
|
+
if (s.startsWith("oklch")) return parseOklch(s);
|
|
1197
|
+
return null;
|
|
1198
|
+
}
|
|
1199
|
+
function relativeLuminance(color) {
|
|
1200
|
+
if (!color) return null;
|
|
1201
|
+
const { r, g, b } = color;
|
|
1202
|
+
return 0.2126 * srgbToLinear(r) + 0.7152 * srgbToLinear(g) + 0.0722 * srgbToLinear(b);
|
|
1203
|
+
}
|
|
1204
|
+
function contrastRatio(a, b) {
|
|
1205
|
+
const la = relativeLuminance(typeof a === "string" ? parseColor(a) : a);
|
|
1206
|
+
const lb = relativeLuminance(typeof b === "string" ? parseColor(b) : b);
|
|
1207
|
+
if (la == null || lb == null) return null;
|
|
1208
|
+
const hi = Math.max(la, lb), lo = Math.min(la, lb);
|
|
1209
|
+
return (hi + 0.05) / (lo + 0.05);
|
|
1210
|
+
}
|
|
1211
|
+
var AA_NORMAL = 4.5;
|
|
1212
|
+
|
|
1213
|
+
// packages/artifact/lib/artifact/ui/tokens.mjs
|
|
1214
|
+
var require22 = createRequire2(new URL("./runtime/packages/artifact/lib/artifact/ui/tokens.mjs", import.meta.url).href);
|
|
1215
|
+
var ARTIFACT_THEME_REGISTRY_PATH = __planrAssetFile2(new URL("./runtime/packages/protocol/registries/artifact-theme.json", import.meta.url));
|
|
1216
|
+
var ARTIFACT_THEME_SCHEMA_PATH = __planrAssetFile2(new URL("./runtime/packages/protocol/schemas/v1.14.0/artifact-theme.schema.json", import.meta.url));
|
|
1217
|
+
var ARTIFACT_THEME_ERROR_CODES = Object.freeze({
|
|
1218
|
+
PARSE: "E_ARTIFACT_THEME_PARSE",
|
|
1219
|
+
SCHEMA: "E_ARTIFACT_THEME_SCHEMA",
|
|
1220
|
+
TOKEN_MISSING: "E_ARTIFACT_THEME_TOKEN_MISSING",
|
|
1221
|
+
TOKEN_UNKNOWN: "E_ARTIFACT_THEME_TOKEN_UNKNOWN",
|
|
1222
|
+
FORMAT: "E_ARTIFACT_THEME_FORMAT",
|
|
1223
|
+
LAYOUT: "E_ARTIFACT_THEME_LAYOUT",
|
|
1224
|
+
MOTION: "E_ARTIFACT_THEME_MOTION",
|
|
1225
|
+
CONTRAST: "E_ARTIFACT_THEME_CONTRAST"
|
|
1226
|
+
});
|
|
1227
|
+
var ArtifactThemeError = class extends Error {
|
|
1228
|
+
constructor(code, message, details = {}) {
|
|
1229
|
+
super(message);
|
|
1230
|
+
this.name = "ArtifactThemeError";
|
|
1231
|
+
this.code = code;
|
|
1232
|
+
this.details = details;
|
|
1233
|
+
}
|
|
1234
|
+
};
|
|
1235
|
+
var TYPOGRAPHY_KEYS = Object.freeze(["display", "body", "mono"]);
|
|
1236
|
+
var LAYOUT_KEYS = Object.freeze([
|
|
1237
|
+
"toolbarHeight",
|
|
1238
|
+
"reviewRailWidth",
|
|
1239
|
+
"radiusSmall",
|
|
1240
|
+
"radiusMedium",
|
|
1241
|
+
"radiusLarge",
|
|
1242
|
+
"motionFastMs",
|
|
1243
|
+
"motionBaseMs"
|
|
1244
|
+
]);
|
|
1245
|
+
var THEME_NAMES = Object.freeze(["dark", "light"]);
|
|
1246
|
+
var COLOR_KEYS = Object.freeze([
|
|
1247
|
+
"background",
|
|
1248
|
+
"chrome",
|
|
1249
|
+
"panel",
|
|
1250
|
+
"raised",
|
|
1251
|
+
"stage",
|
|
1252
|
+
"rule",
|
|
1253
|
+
"text",
|
|
1254
|
+
"textMuted",
|
|
1255
|
+
"primary",
|
|
1256
|
+
"primaryStrong",
|
|
1257
|
+
"onPrimary",
|
|
1258
|
+
"warning",
|
|
1259
|
+
"danger",
|
|
1260
|
+
"onDanger",
|
|
1261
|
+
"question",
|
|
1262
|
+
"onQuestion",
|
|
1263
|
+
"resolved",
|
|
1264
|
+
"onResolved",
|
|
1265
|
+
"onImprove"
|
|
1266
|
+
]);
|
|
1267
|
+
var AA_PAIRS = Object.freeze([
|
|
1268
|
+
["text", "background"],
|
|
1269
|
+
["text", "chrome"],
|
|
1270
|
+
["text", "panel"],
|
|
1271
|
+
["text", "raised"],
|
|
1272
|
+
["text", "stage"],
|
|
1273
|
+
["textMuted", "background"],
|
|
1274
|
+
["textMuted", "panel"],
|
|
1275
|
+
["primary", "background"],
|
|
1276
|
+
["primaryStrong", "background"],
|
|
1277
|
+
["onPrimary", "primary"],
|
|
1278
|
+
["onPrimary", "primaryStrong"],
|
|
1279
|
+
["warning", "background"],
|
|
1280
|
+
["danger", "background"],
|
|
1281
|
+
["onDanger", "danger"],
|
|
1282
|
+
["onQuestion", "question"],
|
|
1283
|
+
["onResolved", "resolved"],
|
|
1284
|
+
["onImprove", "primaryStrong"]
|
|
1285
|
+
]);
|
|
1286
|
+
function readJson(path, label) {
|
|
1287
|
+
try {
|
|
1288
|
+
return JSON.parse(readFileSync2(path, "utf8"));
|
|
1289
|
+
} catch (error) {
|
|
1290
|
+
throw new ArtifactThemeError(
|
|
1291
|
+
ARTIFACT_THEME_ERROR_CODES.PARSE,
|
|
1292
|
+
`Unable to parse ${label}: ${error.message}`,
|
|
1293
|
+
{ path }
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
function schemaCode(issue) {
|
|
1298
|
+
if (issue.rule === "required") return ARTIFACT_THEME_ERROR_CODES.TOKEN_MISSING;
|
|
1299
|
+
if (issue.rule === "additionalProperties") return ARTIFACT_THEME_ERROR_CODES.TOKEN_UNKNOWN;
|
|
1300
|
+
if (issue.rule === "pattern") return ARTIFACT_THEME_ERROR_CODES.FORMAT;
|
|
1301
|
+
if (issue.path.includes(".layout.motion")) return ARTIFACT_THEME_ERROR_CODES.MOTION;
|
|
1302
|
+
if (issue.path.includes(".layout.")) return ARTIFACT_THEME_ERROR_CODES.LAYOUT;
|
|
1303
|
+
return ARTIFACT_THEME_ERROR_CODES.SCHEMA;
|
|
1304
|
+
}
|
|
1305
|
+
function assertSchema(theme, schema) {
|
|
1306
|
+
const issues = validateJson(theme, schema);
|
|
1307
|
+
if (issues.length === 0) return;
|
|
1308
|
+
const first = issues[0];
|
|
1309
|
+
throw new ArtifactThemeError(
|
|
1310
|
+
schemaCode(first),
|
|
1311
|
+
`Invalid artifact theme at ${first.path}: ${first.detail}`,
|
|
1312
|
+
{ issues }
|
|
1313
|
+
);
|
|
1314
|
+
}
|
|
1315
|
+
function assertLayout(layout) {
|
|
1316
|
+
if (layout.toolbarHeight !== 48 || layout.reviewRailWidth !== 344) {
|
|
1317
|
+
throw new ArtifactThemeError(
|
|
1318
|
+
ARTIFACT_THEME_ERROR_CODES.LAYOUT,
|
|
1319
|
+
"Artifact shell layout must keep a 48px toolbar and 344px review rail.",
|
|
1320
|
+
{ toolbarHeight: layout.toolbarHeight, reviewRailWidth: layout.reviewRailWidth }
|
|
1321
|
+
);
|
|
1322
|
+
}
|
|
1323
|
+
const radii = ["radiusSmall", "radiusMedium", "radiusLarge"].map((key) => [key, layout[key]]);
|
|
1324
|
+
const invalidRadius = radii.find(
|
|
1325
|
+
([, value]) => !Number.isInteger(value) || value < 0 || value > 32
|
|
1326
|
+
);
|
|
1327
|
+
if (invalidRadius) {
|
|
1328
|
+
throw new ArtifactThemeError(
|
|
1329
|
+
ARTIFACT_THEME_ERROR_CODES.LAYOUT,
|
|
1330
|
+
`${invalidRadius[0]} must be an integer from 0 through 32.`,
|
|
1331
|
+
{ token: invalidRadius[0], value: invalidRadius[1] }
|
|
1332
|
+
);
|
|
1333
|
+
}
|
|
1334
|
+
if (!(layout.radiusSmall <= layout.radiusMedium && layout.radiusMedium <= layout.radiusLarge)) {
|
|
1335
|
+
throw new ArtifactThemeError(
|
|
1336
|
+
ARTIFACT_THEME_ERROR_CODES.LAYOUT,
|
|
1337
|
+
"Artifact shell radii must be ordered small <= medium <= large.",
|
|
1338
|
+
{ radii: Object.fromEntries(radii) }
|
|
1339
|
+
);
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
function assertMotion(layout) {
|
|
1343
|
+
for (const token of ["motionFastMs", "motionBaseMs"]) {
|
|
1344
|
+
const value = layout[token];
|
|
1345
|
+
if (!Number.isInteger(value) || value < 120 || value > 200) {
|
|
1346
|
+
throw new ArtifactThemeError(
|
|
1347
|
+
ARTIFACT_THEME_ERROR_CODES.MOTION,
|
|
1348
|
+
`${token} must be an integer from 120 through 200 milliseconds.`,
|
|
1349
|
+
{ token, value }
|
|
1350
|
+
);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
if (layout.motionFastMs > layout.motionBaseMs) {
|
|
1354
|
+
throw new ArtifactThemeError(
|
|
1355
|
+
ARTIFACT_THEME_ERROR_CODES.MOTION,
|
|
1356
|
+
"motionFastMs must not exceed motionBaseMs.",
|
|
1357
|
+
{ motionFastMs: layout.motionFastMs, motionBaseMs: layout.motionBaseMs }
|
|
1358
|
+
);
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
function assertContrast(themes) {
|
|
1362
|
+
for (const themeName of THEME_NAMES) {
|
|
1363
|
+
const colors = themes[themeName];
|
|
1364
|
+
for (const [foreground, background] of AA_PAIRS) {
|
|
1365
|
+
const ratio = contrastRatio(colors[foreground], colors[background]);
|
|
1366
|
+
if (ratio == null || ratio < 4.5) {
|
|
1367
|
+
throw new ArtifactThemeError(
|
|
1368
|
+
ARTIFACT_THEME_ERROR_CODES.CONTRAST,
|
|
1369
|
+
`${themeName}.${foreground} on ${themeName}.${background} must meet WCAG AA (4.5:1).`,
|
|
1370
|
+
{ theme: themeName, foreground, background, ratio }
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
}
|
|
1376
|
+
function validateArtifactTheme(theme, { schema } = {}) {
|
|
1377
|
+
const contract = schema ?? readJson(ARTIFACT_THEME_SCHEMA_PATH, "artifact theme schema");
|
|
1378
|
+
assertSchema(theme, contract);
|
|
1379
|
+
assertLayout(theme.layout);
|
|
1380
|
+
assertMotion(theme.layout);
|
|
1381
|
+
assertContrast(theme.themes);
|
|
1382
|
+
return theme;
|
|
1383
|
+
}
|
|
1384
|
+
function pick(source, keys) {
|
|
1385
|
+
return Object.fromEntries(keys.map((key) => [key, source[key]]));
|
|
1386
|
+
}
|
|
1387
|
+
function normalizeArtifactTheme(theme) {
|
|
1388
|
+
validateArtifactTheme(theme);
|
|
1389
|
+
return {
|
|
1390
|
+
kind: theme.kind,
|
|
1391
|
+
schemaVersion: theme.schemaVersion,
|
|
1392
|
+
protocolVersion: theme.protocolVersion,
|
|
1393
|
+
name: theme.name,
|
|
1394
|
+
typography: pick(theme.typography, TYPOGRAPHY_KEYS),
|
|
1395
|
+
layout: pick(theme.layout, LAYOUT_KEYS),
|
|
1396
|
+
themes: Object.fromEntries(
|
|
1397
|
+
THEME_NAMES.map((themeName) => [themeName, pick(theme.themes[themeName], COLOR_KEYS)])
|
|
1398
|
+
)
|
|
1399
|
+
};
|
|
1400
|
+
}
|
|
1401
|
+
function loadArtifactTheme({
|
|
1402
|
+
registryPath = ARTIFACT_THEME_REGISTRY_PATH,
|
|
1403
|
+
schemaPath = ARTIFACT_THEME_SCHEMA_PATH
|
|
1404
|
+
} = {}) {
|
|
1405
|
+
const registry = readJson(registryPath, "artifact theme registry");
|
|
1406
|
+
const schema = readJson(schemaPath, "artifact theme schema");
|
|
1407
|
+
validateArtifactTheme(registry, { schema });
|
|
1408
|
+
return normalizeArtifactTheme(registry);
|
|
1409
|
+
}
|
|
1410
|
+
function kebab(value) {
|
|
1411
|
+
return value.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
|
|
1412
|
+
}
|
|
1413
|
+
function colorVariables(colors) {
|
|
1414
|
+
return COLOR_KEYS.map((key) => ` --planr-color-${kebab(key)}: ${colors[key]};`).join("\n");
|
|
1415
|
+
}
|
|
1416
|
+
function renderArtifactThemeCss(theme) {
|
|
1417
|
+
const value = normalizeArtifactTheme(theme);
|
|
1418
|
+
const { typography, layout, themes } = value;
|
|
1419
|
+
const foundations = [
|
|
1420
|
+
` --planr-font-display: "${typography.display}", ui-sans-serif, system-ui, sans-serif;`,
|
|
1421
|
+
` --planr-font-body: "${typography.body}", ui-sans-serif, system-ui, sans-serif;`,
|
|
1422
|
+
` --planr-font-mono: "${typography.mono}", ui-monospace, SFMono-Regular, Consolas, monospace;`,
|
|
1423
|
+
` --planr-toolbar-height: ${layout.toolbarHeight}px;`,
|
|
1424
|
+
` --planr-review-rail-width: ${layout.reviewRailWidth}px;`,
|
|
1425
|
+
` --planr-radius-small: ${layout.radiusSmall}px;`,
|
|
1426
|
+
` --planr-radius-medium: ${layout.radiusMedium}px;`,
|
|
1427
|
+
` --planr-radius-large: ${layout.radiusLarge}px;`,
|
|
1428
|
+
` --planr-motion-fast: ${layout.motionFastMs}ms;`,
|
|
1429
|
+
` --planr-motion-base: ${layout.motionBaseMs}ms;`
|
|
1430
|
+
].join("\n");
|
|
1431
|
+
return [
|
|
1432
|
+
"/* Generated by scripts/generate-artifact-shell.mjs. Do not edit. */",
|
|
1433
|
+
":root,",
|
|
1434
|
+
'[data-planr-theme="dark"] {',
|
|
1435
|
+
foundations,
|
|
1436
|
+
colorVariables(themes.dark),
|
|
1437
|
+
"}",
|
|
1438
|
+
"",
|
|
1439
|
+
'[data-planr-theme="light"] {',
|
|
1440
|
+
colorVariables(themes.light),
|
|
1441
|
+
"}",
|
|
1442
|
+
"",
|
|
1443
|
+
"@media (prefers-color-scheme: light) {",
|
|
1444
|
+
" :root:not([data-planr-theme]),",
|
|
1445
|
+
' [data-planr-theme="auto"] {',
|
|
1446
|
+
colorVariables(themes.light).replaceAll(/^/gm, " "),
|
|
1447
|
+
" }",
|
|
1448
|
+
"}",
|
|
1449
|
+
"",
|
|
1450
|
+
"@media (prefers-reduced-motion: reduce) {",
|
|
1451
|
+
" :root {",
|
|
1452
|
+
" --planr-motion-fast: 0ms;",
|
|
1453
|
+
" --planr-motion-base: 0ms;",
|
|
1454
|
+
" }",
|
|
1455
|
+
"}",
|
|
1456
|
+
""
|
|
1457
|
+
].join("\n");
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// packages/artifact/lib/artifact/ui/shell.mjs
|
|
1461
|
+
var ARTIFACT_SHELL_VERSION = "1.2.1";
|
|
1462
|
+
var ARTIFACT_SHELL_ASSET_PATHS = Object.freeze({
|
|
1463
|
+
template: "templates/artifact-review-shell.html",
|
|
1464
|
+
stageRuntime: "templates/artifact-review-stage.js",
|
|
1465
|
+
manifest: "lib/artifact/ui/generated/artifact-shell-assets.json",
|
|
1466
|
+
themeCss: "lib/artifact/ui/generated/artifact-theme.css",
|
|
1467
|
+
themeJson: "lib/artifact/ui/generated/artifact-theme.json"
|
|
1468
|
+
});
|
|
1469
|
+
var ARTIFACT_SHELL_CSS = `
|
|
1470
|
+
* { box-sizing: border-box; }
|
|
1471
|
+
html, body { width: 100%; height: 100%; margin: 0; }
|
|
1472
|
+
body {
|
|
1473
|
+
overflow: hidden;
|
|
1474
|
+
background: var(--planr-color-background);
|
|
1475
|
+
color: var(--planr-color-text);
|
|
1476
|
+
font: 400 13px/1.45 var(--planr-font-body);
|
|
1477
|
+
-webkit-font-smoothing: antialiased;
|
|
1478
|
+
}
|
|
1479
|
+
button, input, select, textarea { font: inherit; }
|
|
1480
|
+
button { color: inherit; }
|
|
1481
|
+
button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-visible,
|
|
1482
|
+
[tabindex]:focus-visible {
|
|
1483
|
+
outline: 2px solid var(--planr-color-primary);
|
|
1484
|
+
outline-offset: 2px;
|
|
1485
|
+
}
|
|
1486
|
+
[hidden] { display: none !important; }
|
|
1487
|
+
.planr-visually-hidden {
|
|
1488
|
+
position: fixed;
|
|
1489
|
+
width: 1px;
|
|
1490
|
+
height: 1px;
|
|
1491
|
+
overflow: hidden;
|
|
1492
|
+
clip-path: inset(50%);
|
|
1493
|
+
white-space: nowrap;
|
|
1494
|
+
}
|
|
1495
|
+
.planr-shell {
|
|
1496
|
+
height: 100%;
|
|
1497
|
+
display: grid;
|
|
1498
|
+
grid-template-rows: var(--planr-toolbar-height) minmax(0, 1fr);
|
|
1499
|
+
background: var(--planr-color-background);
|
|
1500
|
+
}
|
|
1501
|
+
.planr-toolbar {
|
|
1502
|
+
position: relative;
|
|
1503
|
+
z-index: 40;
|
|
1504
|
+
min-width: 0;
|
|
1505
|
+
display: flex;
|
|
1506
|
+
align-items: center;
|
|
1507
|
+
gap: 10px;
|
|
1508
|
+
padding: 0 10px 0 14px;
|
|
1509
|
+
border-bottom: 1px solid var(--planr-color-rule);
|
|
1510
|
+
background: color-mix(in srgb, var(--planr-color-chrome) 96%, transparent);
|
|
1511
|
+
}
|
|
1512
|
+
.planr-brand { min-width: 0; display: flex; align-items: center; gap: 9px; }
|
|
1513
|
+
.planr-mark { display: inline-grid; place-items: center; width: 22px; height: 22px; flex: none; color: var(--planr-color-primary); contain: paint; }
|
|
1514
|
+
.planr-mark svg { display: block; width: 100%; height: 100%; }
|
|
1515
|
+
.planr-title-block { min-width: 0; display: flex; align-items: baseline; gap: 8px; }
|
|
1516
|
+
.planr-title-block strong {
|
|
1517
|
+
max-width: 260px;
|
|
1518
|
+
overflow: hidden;
|
|
1519
|
+
color: var(--planr-color-text);
|
|
1520
|
+
font: 650 13px/1 var(--planr-font-display);
|
|
1521
|
+
letter-spacing: -.01em;
|
|
1522
|
+
text-overflow: ellipsis;
|
|
1523
|
+
white-space: nowrap;
|
|
1524
|
+
}
|
|
1525
|
+
.planr-title-block span, .planr-privacy {
|
|
1526
|
+
color: var(--planr-color-text-muted);
|
|
1527
|
+
font: 10px/1 var(--planr-font-mono);
|
|
1528
|
+
white-space: nowrap;
|
|
1529
|
+
}
|
|
1530
|
+
.planr-privacy { display: inline-flex; align-items: center; gap: 6px; font-family: var(--planr-font-body); }
|
|
1531
|
+
.planr-privacy::before {
|
|
1532
|
+
content: "";
|
|
1533
|
+
width: 6px;
|
|
1534
|
+
height: 6px;
|
|
1535
|
+
border-radius: 50%;
|
|
1536
|
+
background: var(--planr-color-primary);
|
|
1537
|
+
box-shadow: 0 0 0 3px color-mix(in srgb, var(--planr-color-primary) 14%, transparent);
|
|
1538
|
+
}
|
|
1539
|
+
.planr-toolbar-spacer { flex: 1; }
|
|
1540
|
+
.planr-room-manager { display: inline-flex; align-items: center; gap: 5px; }
|
|
1541
|
+
.planr-room-manager button { height: 28px; border: 1px solid var(--planr-color-rule); border-radius: var(--planr-radius-small); background: var(--planr-color-panel); color: var(--planr-color-text-muted); font: 650 10px/1 var(--planr-font-body); padding: 0 8px; }
|
|
1542
|
+
.planr-room-manager button:last-child { border-color: color-mix(in srgb, var(--planr-color-danger) 55%, var(--planr-color-rule)); color: var(--planr-color-danger); }
|
|
1543
|
+
.planr-shell[data-planr-room-comments-paused] [data-planr-mode="comment"],
|
|
1544
|
+
.planr-shell[data-planr-room-comments-paused] [data-planr-action="add-comment"] { cursor: not-allowed; opacity: .48; }
|
|
1545
|
+
.planr-domain-toolbar { min-width: 0; display: inline-flex; align-items: center; gap: 6px; }
|
|
1546
|
+
.planr-segment {
|
|
1547
|
+
height: 30px;
|
|
1548
|
+
display: inline-flex;
|
|
1549
|
+
align-items: center;
|
|
1550
|
+
gap: 2px;
|
|
1551
|
+
padding: 2px;
|
|
1552
|
+
border: 1px solid var(--planr-color-rule);
|
|
1553
|
+
border-radius: var(--planr-radius-medium);
|
|
1554
|
+
background: var(--planr-color-background);
|
|
1555
|
+
}
|
|
1556
|
+
.planr-segment button, .planr-toolbar-action {
|
|
1557
|
+
height: 24px;
|
|
1558
|
+
border: 0;
|
|
1559
|
+
border-radius: var(--planr-radius-small);
|
|
1560
|
+
background: transparent;
|
|
1561
|
+
color: var(--planr-color-text-muted);
|
|
1562
|
+
cursor: pointer;
|
|
1563
|
+
transition: color var(--planr-motion-fast), background var(--planr-motion-fast), transform var(--planr-motion-fast);
|
|
1564
|
+
}
|
|
1565
|
+
.planr-segment button { min-width: 26px; padding: 0 8px; font-size: 11px; font-weight: 650; }
|
|
1566
|
+
.planr-segment button[aria-pressed="true"] {
|
|
1567
|
+
background: var(--planr-color-primary);
|
|
1568
|
+
color: var(--planr-color-background);
|
|
1569
|
+
}
|
|
1570
|
+
.planr-segment button:disabled { opacity: .45; cursor: not-allowed; }
|
|
1571
|
+
.planr-toolbar-action {
|
|
1572
|
+
height: 30px;
|
|
1573
|
+
padding: 0 12px;
|
|
1574
|
+
border: 1px solid var(--planr-color-rule);
|
|
1575
|
+
color: var(--planr-color-text);
|
|
1576
|
+
font-weight: 700;
|
|
1577
|
+
text-transform: capitalize;
|
|
1578
|
+
}
|
|
1579
|
+
.planr-toolbar-action:hover, .planr-segment button:not(:disabled):hover { background: var(--planr-color-raised); color: var(--planr-color-text); }
|
|
1580
|
+
.planr-toolbar-action:active, .planr-segment button:not(:disabled):active { transform: scale(.97); }
|
|
1581
|
+
.planr-share { border-color: var(--planr-color-primary); background: var(--planr-color-primary); color: var(--planr-color-background); }
|
|
1582
|
+
.planr-count {
|
|
1583
|
+
min-width: 18px;
|
|
1584
|
+
height: 18px;
|
|
1585
|
+
display: inline-grid;
|
|
1586
|
+
place-items: center;
|
|
1587
|
+
padding-inline: 4px;
|
|
1588
|
+
border-radius: 999px;
|
|
1589
|
+
background: var(--planr-color-raised);
|
|
1590
|
+
color: var(--planr-color-text-muted);
|
|
1591
|
+
font: 10px/1 var(--planr-font-mono);
|
|
1592
|
+
}
|
|
1593
|
+
.planr-floating-actions {
|
|
1594
|
+
position: fixed;
|
|
1595
|
+
z-index: 45;
|
|
1596
|
+
top: 50%;
|
|
1597
|
+
right: max(12px, env(safe-area-inset-right));
|
|
1598
|
+
display: flex;
|
|
1599
|
+
flex-direction: column;
|
|
1600
|
+
gap: 4px;
|
|
1601
|
+
padding: 5px;
|
|
1602
|
+
border: 1px solid var(--planr-color-rule);
|
|
1603
|
+
border-radius: var(--planr-radius-large);
|
|
1604
|
+
background: color-mix(in srgb, var(--planr-color-chrome) 94%, transparent);
|
|
1605
|
+
box-shadow: 0 12px 36px color-mix(in srgb, var(--planr-color-background) 38%, transparent);
|
|
1606
|
+
backdrop-filter: blur(16px);
|
|
1607
|
+
transform: translateY(-50%);
|
|
1608
|
+
}
|
|
1609
|
+
.planr-floating-actions > button,
|
|
1610
|
+
.planr-floating-actions > .planr-more > button {
|
|
1611
|
+
position: relative;
|
|
1612
|
+
width: 40px;
|
|
1613
|
+
height: 40px;
|
|
1614
|
+
display: grid;
|
|
1615
|
+
place-items: center;
|
|
1616
|
+
border: 0;
|
|
1617
|
+
border-radius: var(--planr-radius-medium);
|
|
1618
|
+
background: transparent;
|
|
1619
|
+
color: var(--planr-color-text);
|
|
1620
|
+
cursor: pointer;
|
|
1621
|
+
transition: color var(--planr-motion-fast), background var(--planr-motion-fast), transform var(--planr-motion-fast);
|
|
1622
|
+
}
|
|
1623
|
+
.planr-floating-actions button:hover,
|
|
1624
|
+
.planr-floating-actions button[aria-pressed="true"],
|
|
1625
|
+
.planr-floating-actions button[aria-expanded="true"] {
|
|
1626
|
+
background: var(--planr-color-raised);
|
|
1627
|
+
color: var(--planr-color-primary);
|
|
1628
|
+
}
|
|
1629
|
+
.planr-floating-actions button:active { transform: scale(.96); }
|
|
1630
|
+
.planr-floating-actions button:disabled { cursor: not-allowed; opacity: .48; }
|
|
1631
|
+
.planr-floating-actions svg {
|
|
1632
|
+
width: 20px;
|
|
1633
|
+
height: 20px;
|
|
1634
|
+
fill: none;
|
|
1635
|
+
stroke: currentColor;
|
|
1636
|
+
stroke-linecap: round;
|
|
1637
|
+
stroke-linejoin: round;
|
|
1638
|
+
stroke-width: 1.8;
|
|
1639
|
+
}
|
|
1640
|
+
.planr-floating-actions svg circle { fill: currentColor; stroke: none; }
|
|
1641
|
+
.planr-floating-actions [data-planr-action="feedback"] .planr-count {
|
|
1642
|
+
position: absolute;
|
|
1643
|
+
top: 2px;
|
|
1644
|
+
right: 1px;
|
|
1645
|
+
min-width: 16px;
|
|
1646
|
+
height: 16px;
|
|
1647
|
+
padding-inline: 3px;
|
|
1648
|
+
background: var(--planr-color-primary);
|
|
1649
|
+
color: var(--planr-color-background);
|
|
1650
|
+
font-size: 9px;
|
|
1651
|
+
}
|
|
1652
|
+
.planr-floating-actions [data-planr-tooltip]::after {
|
|
1653
|
+
content: attr(data-planr-tooltip);
|
|
1654
|
+
position: absolute;
|
|
1655
|
+
top: 50%;
|
|
1656
|
+
right: calc(100% + 10px);
|
|
1657
|
+
width: max-content;
|
|
1658
|
+
max-width: 220px;
|
|
1659
|
+
padding: 6px 8px;
|
|
1660
|
+
border-radius: var(--planr-radius-small);
|
|
1661
|
+
background: var(--planr-color-text);
|
|
1662
|
+
color: var(--planr-color-background);
|
|
1663
|
+
font: 650 10px/1.2 var(--planr-font-body);
|
|
1664
|
+
opacity: 0;
|
|
1665
|
+
pointer-events: none;
|
|
1666
|
+
transform: translate(4px, -50%);
|
|
1667
|
+
transition: opacity var(--planr-motion-fast), transform var(--planr-motion-fast);
|
|
1668
|
+
}
|
|
1669
|
+
.planr-floating-actions [data-planr-tooltip]:hover::after,
|
|
1670
|
+
.planr-floating-actions [data-planr-tooltip]:focus-visible::after {
|
|
1671
|
+
opacity: 1;
|
|
1672
|
+
transform: translate(0, -50%);
|
|
1673
|
+
}
|
|
1674
|
+
.planr-action-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
1675
|
+
.planr-more { position: relative; }
|
|
1676
|
+
.planr-more-menu {
|
|
1677
|
+
position: absolute;
|
|
1678
|
+
right: calc(100% + 10px);
|
|
1679
|
+
bottom: 0;
|
|
1680
|
+
width: 250px;
|
|
1681
|
+
padding: 8px;
|
|
1682
|
+
border: 1px solid var(--planr-color-rule);
|
|
1683
|
+
border-radius: var(--planr-radius-medium);
|
|
1684
|
+
background: var(--planr-color-panel);
|
|
1685
|
+
box-shadow: 0 16px 48px color-mix(in srgb, var(--planr-color-background) 42%, transparent);
|
|
1686
|
+
}
|
|
1687
|
+
.planr-more-menu > div:first-child { display: grid; gap: 2px; padding: 7px 8px 10px; }
|
|
1688
|
+
.planr-more-menu strong { font-size: 12px; }
|
|
1689
|
+
.planr-more-menu span { color: var(--planr-color-text-muted); font-size: 10px; }
|
|
1690
|
+
.planr-more-menu > button {
|
|
1691
|
+
width: 100%;
|
|
1692
|
+
min-height: 36px;
|
|
1693
|
+
padding: 0 8px;
|
|
1694
|
+
border: 0;
|
|
1695
|
+
border-radius: var(--planr-radius-small);
|
|
1696
|
+
background: transparent;
|
|
1697
|
+
text-align: left;
|
|
1698
|
+
cursor: pointer;
|
|
1699
|
+
}
|
|
1700
|
+
.planr-more-menu > button:hover { background: var(--planr-color-raised); }
|
|
1701
|
+
.planr-comments-scrim {
|
|
1702
|
+
position: fixed;
|
|
1703
|
+
z-index: 40;
|
|
1704
|
+
inset: 0;
|
|
1705
|
+
border: 0;
|
|
1706
|
+
background: color-mix(in srgb, var(--planr-color-background) 8%, transparent);
|
|
1707
|
+
cursor: default;
|
|
1708
|
+
opacity: 1;
|
|
1709
|
+
transition: opacity var(--planr-motion-fast);
|
|
1710
|
+
}
|
|
1711
|
+
.planr-shell[data-planr-rail-open="false"] .planr-comments-scrim {
|
|
1712
|
+
opacity: 0;
|
|
1713
|
+
pointer-events: none;
|
|
1714
|
+
}
|
|
1715
|
+
.planr-workspace {
|
|
1716
|
+
min-width: 0;
|
|
1717
|
+
min-height: 0;
|
|
1718
|
+
display: grid;
|
|
1719
|
+
grid-template-columns: minmax(0, 1fr) var(--planr-review-rail-width);
|
|
1720
|
+
transition: grid-template-columns var(--planr-motion-base);
|
|
1721
|
+
}
|
|
1722
|
+
.planr-shell[data-planr-rail-open="false"] .planr-workspace { grid-template-columns: minmax(0, 1fr) 0; }
|
|
1723
|
+
.planr-stage {
|
|
1724
|
+
position: relative;
|
|
1725
|
+
min-width: 0;
|
|
1726
|
+
min-height: 0;
|
|
1727
|
+
overflow: hidden;
|
|
1728
|
+
background-color: var(--planr-color-stage);
|
|
1729
|
+
background-image: radial-gradient(color-mix(in srgb, var(--planr-color-text-muted) 20%, transparent) 1px, transparent 1px);
|
|
1730
|
+
background-size: 20px 20px;
|
|
1731
|
+
}
|
|
1732
|
+
.planr-stage-heading {
|
|
1733
|
+
position: absolute;
|
|
1734
|
+
z-index: 12;
|
|
1735
|
+
inset: 0 0 auto;
|
|
1736
|
+
height: 34px;
|
|
1737
|
+
display: flex;
|
|
1738
|
+
align-items: center;
|
|
1739
|
+
justify-content: space-between;
|
|
1740
|
+
padding: 0 14px;
|
|
1741
|
+
color: var(--planr-color-text-muted);
|
|
1742
|
+
font: 10px/1 var(--planr-font-mono);
|
|
1743
|
+
pointer-events: none;
|
|
1744
|
+
}
|
|
1745
|
+
.planr-stage-controls {
|
|
1746
|
+
position: absolute;
|
|
1747
|
+
z-index: 13;
|
|
1748
|
+
inset: 34px 0 auto;
|
|
1749
|
+
min-width: 0;
|
|
1750
|
+
min-height: 42px;
|
|
1751
|
+
display: flex;
|
|
1752
|
+
align-items: center;
|
|
1753
|
+
gap: 8px;
|
|
1754
|
+
padding: 5px 14px 7px;
|
|
1755
|
+
background: linear-gradient(var(--planr-color-stage) 72%, transparent);
|
|
1756
|
+
}
|
|
1757
|
+
.planr-view-modes { flex: none; }
|
|
1758
|
+
.planr-variants {
|
|
1759
|
+
min-width: 0;
|
|
1760
|
+
display: flex;
|
|
1761
|
+
align-items: center;
|
|
1762
|
+
gap: 5px;
|
|
1763
|
+
overflow: auto;
|
|
1764
|
+
scrollbar-width: none;
|
|
1765
|
+
}
|
|
1766
|
+
.planr-variants button {
|
|
1767
|
+
flex: none;
|
|
1768
|
+
height: 28px;
|
|
1769
|
+
max-width: 220px;
|
|
1770
|
+
overflow: hidden;
|
|
1771
|
+
padding: 0 10px;
|
|
1772
|
+
border: 1px solid var(--planr-color-rule);
|
|
1773
|
+
border-radius: var(--planr-radius-small);
|
|
1774
|
+
background: var(--planr-color-chrome);
|
|
1775
|
+
color: var(--planr-color-text-muted);
|
|
1776
|
+
cursor: pointer;
|
|
1777
|
+
font-size: 10px;
|
|
1778
|
+
font-weight: 650;
|
|
1779
|
+
text-overflow: ellipsis;
|
|
1780
|
+
white-space: nowrap;
|
|
1781
|
+
}
|
|
1782
|
+
.planr-variants button span { font-family: var(--planr-font-mono); }
|
|
1783
|
+
.planr-variants button[aria-selected="true"] { border-color: var(--planr-color-primary); color: var(--planr-color-text); }
|
|
1784
|
+
.planr-stage-scroll { position: absolute; inset: 76px 0 0; overflow: auto; padding: 8px 34px 64px; }
|
|
1785
|
+
.planr-stage-surface {
|
|
1786
|
+
width: max-content;
|
|
1787
|
+
min-width: 100%;
|
|
1788
|
+
transform: scale(var(--planr-shell-zoom));
|
|
1789
|
+
transform-origin: top left;
|
|
1790
|
+
transition: transform var(--planr-motion-fast);
|
|
1791
|
+
}
|
|
1792
|
+
.planr-frame-grid { width: max-content; display: grid; grid-auto-flow: column; grid-auto-columns: max-content; gap: 12px; }
|
|
1793
|
+
.planr-frame-grid[data-planr-layout="single"] .planr-frame-label { display: none; }
|
|
1794
|
+
.planr-artifact-panel {
|
|
1795
|
+
position: relative;
|
|
1796
|
+
width: var(--planr-artifact-width);
|
|
1797
|
+
height: var(--planr-artifact-height);
|
|
1798
|
+
}
|
|
1799
|
+
.planr-frame {
|
|
1800
|
+
width: 100%;
|
|
1801
|
+
height: 100%;
|
|
1802
|
+
overflow: hidden;
|
|
1803
|
+
border: 0;
|
|
1804
|
+
border-radius: var(--planr-radius-medium);
|
|
1805
|
+
background: var(--planr-color-chrome);
|
|
1806
|
+
box-shadow: inset 0 0 0 1px var(--planr-color-rule), 0 18px 60px color-mix(in srgb, var(--planr-color-background) 52%, transparent);
|
|
1807
|
+
}
|
|
1808
|
+
.planr-frame iframe { width: 100%; height: 100%; display: block; border: 0; background: var(--planr-color-chrome); }
|
|
1809
|
+
.planr-frame-label {
|
|
1810
|
+
position: absolute;
|
|
1811
|
+
z-index: 4;
|
|
1812
|
+
inset: 10px auto auto 10px;
|
|
1813
|
+
padding: 5px 7px;
|
|
1814
|
+
border-radius: var(--planr-radius-small);
|
|
1815
|
+
background: color-mix(in srgb, var(--planr-color-background) 78%, transparent);
|
|
1816
|
+
color: var(--planr-color-text);
|
|
1817
|
+
font: 9px/1 var(--planr-font-mono);
|
|
1818
|
+
pointer-events: none;
|
|
1819
|
+
}
|
|
1820
|
+
.planr-annotation-layer { position: absolute; z-index: 6; inset: 0; }
|
|
1821
|
+
.planr-shell[data-planr-review-mode="interact"] .planr-annotation-layer { pointer-events: none; }
|
|
1822
|
+
.planr-shell[data-planr-review-mode="comment"] .planr-annotation-layer { cursor: crosshair; touch-action: none; }
|
|
1823
|
+
.planr-shell[data-planr-review-mode="comment"] .planr-frame iframe { pointer-events: none; }
|
|
1824
|
+
.planr-region-selection {
|
|
1825
|
+
position: absolute;
|
|
1826
|
+
min-width: 2px;
|
|
1827
|
+
min-height: 2px;
|
|
1828
|
+
border: 2px solid var(--planr-color-primary);
|
|
1829
|
+
border-radius: var(--planr-radius-small);
|
|
1830
|
+
background: color-mix(in srgb, var(--planr-color-primary) 14%, transparent);
|
|
1831
|
+
pointer-events: none;
|
|
1832
|
+
}
|
|
1833
|
+
.planr-pin {
|
|
1834
|
+
position: absolute;
|
|
1835
|
+
z-index: 8;
|
|
1836
|
+
width: 44px;
|
|
1837
|
+
height: 44px;
|
|
1838
|
+
min-width: 44px;
|
|
1839
|
+
min-height: 44px;
|
|
1840
|
+
translate: -50% -50%;
|
|
1841
|
+
display: grid;
|
|
1842
|
+
place-items: center;
|
|
1843
|
+
padding: 0;
|
|
1844
|
+
border: 8px solid transparent;
|
|
1845
|
+
border-radius: 50%;
|
|
1846
|
+
background-clip: padding-box;
|
|
1847
|
+
color: var(--planr-color-on-danger);
|
|
1848
|
+
cursor: pointer;
|
|
1849
|
+
pointer-events: auto;
|
|
1850
|
+
font: 700 10px/1 var(--planr-font-mono);
|
|
1851
|
+
box-shadow: 0 4px 14px color-mix(in srgb, var(--planr-color-background) 38%, transparent);
|
|
1852
|
+
}
|
|
1853
|
+
.planr-pin-fix { background-color: var(--planr-color-danger); }
|
|
1854
|
+
.planr-pin-improve { background-color: var(--planr-color-primary-strong); color: var(--planr-color-on-improve); }
|
|
1855
|
+
.planr-pin-question { background-color: var(--planr-color-question); color: var(--planr-color-on-question); }
|
|
1856
|
+
.planr-pin-resolved, .planr-pin-addressed { background-color: var(--planr-color-resolved); color: var(--planr-color-on-resolved); }
|
|
1857
|
+
.planr-pin-highlight { animation: planr-pin-highlight 1.2s ease-out; }
|
|
1858
|
+
@keyframes planr-pin-highlight {
|
|
1859
|
+
0%, 35% { box-shadow: 0 0 0 7px color-mix(in srgb, var(--planr-color-primary) 52%, transparent), 0 4px 14px color-mix(in srgb, var(--planr-color-background) 38%, transparent); }
|
|
1860
|
+
100% { box-shadow: 0 4px 14px color-mix(in srgb, var(--planr-color-background) 38%, transparent); }
|
|
1861
|
+
}
|
|
1862
|
+
.planr-pin-region {
|
|
1863
|
+
position: absolute;
|
|
1864
|
+
z-index: 7;
|
|
1865
|
+
min-width: 2px;
|
|
1866
|
+
min-height: 2px;
|
|
1867
|
+
border: 2px solid var(--planr-color-danger);
|
|
1868
|
+
border-radius: var(--planr-radius-small);
|
|
1869
|
+
background: color-mix(in srgb, var(--planr-color-danger) 12%, transparent);
|
|
1870
|
+
pointer-events: none;
|
|
1871
|
+
}
|
|
1872
|
+
.planr-pin-region-improve { border-color: var(--planr-color-primary-strong); background: color-mix(in srgb, var(--planr-color-primary-strong) 12%, transparent); }
|
|
1873
|
+
.planr-pin-region-question { border-color: var(--planr-color-question); background: color-mix(in srgb, var(--planr-color-question) 12%, transparent); }
|
|
1874
|
+
.planr-pin-region-resolved, .planr-pin-region-addressed { border-color: var(--planr-color-resolved); background: color-mix(in srgb, var(--planr-color-resolved) 10%, transparent); }
|
|
1875
|
+
.planr-annotation-composer {
|
|
1876
|
+
position: fixed;
|
|
1877
|
+
inset: auto;
|
|
1878
|
+
z-index: 100;
|
|
1879
|
+
box-sizing: border-box;
|
|
1880
|
+
width: min(360px, calc(100vw - 24px));
|
|
1881
|
+
max-height: min(620px, calc(100dvh - 24px));
|
|
1882
|
+
margin: 0;
|
|
1883
|
+
overflow: auto;
|
|
1884
|
+
overscroll-behavior: contain;
|
|
1885
|
+
translate: none;
|
|
1886
|
+
transform: none;
|
|
1887
|
+
padding: 14px;
|
|
1888
|
+
border: 1px solid var(--planr-color-rule);
|
|
1889
|
+
border-radius: var(--planr-radius-large);
|
|
1890
|
+
background: var(--planr-color-panel);
|
|
1891
|
+
color: var(--planr-color-text);
|
|
1892
|
+
box-shadow: 0 18px 60px color-mix(in srgb, var(--planr-color-background) 58%, transparent);
|
|
1893
|
+
cursor: default;
|
|
1894
|
+
pointer-events: auto;
|
|
1895
|
+
}
|
|
1896
|
+
.planr-annotation-composer strong { display: block; margin: 0; font: 650 14px/1.2 var(--planr-font-display); }
|
|
1897
|
+
.planr-composer-header { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 12px; }
|
|
1898
|
+
.planr-composer-header button { display: grid; place-items: center; flex: 0 0 28px; width: 28px; height: 28px; padding: 0; border: 1px solid transparent; border-radius: var(--planr-radius-small); background: transparent; color: var(--planr-color-text-muted); font: 22px/1 var(--planr-font-body); cursor: pointer; }
|
|
1899
|
+
.planr-composer-header button:hover { background: var(--planr-color-raised); color: var(--planr-color-text); }
|
|
1900
|
+
.planr-annotation-composer :focus-visible { outline: 2px solid var(--planr-color-primary); outline-offset: 2px; }
|
|
1901
|
+
.planr-annotation-composer::backdrop { background: transparent; pointer-events: none; }
|
|
1902
|
+
.planr-annotation-composer label, .planr-identity label {
|
|
1903
|
+
display: grid;
|
|
1904
|
+
gap: 5px;
|
|
1905
|
+
margin-bottom: 9px;
|
|
1906
|
+
color: var(--planr-color-text-muted);
|
|
1907
|
+
font-size: 11px;
|
|
1908
|
+
}
|
|
1909
|
+
.planr-annotation-composer input, .planr-annotation-composer textarea, .planr-identity input,
|
|
1910
|
+
.planr-reply-form textarea {
|
|
1911
|
+
width: 100%;
|
|
1912
|
+
padding: 8px 9px;
|
|
1913
|
+
border: 1px solid var(--planr-color-rule);
|
|
1914
|
+
border-radius: var(--planr-radius-small);
|
|
1915
|
+
background: var(--planr-color-chrome);
|
|
1916
|
+
color: var(--planr-color-text);
|
|
1917
|
+
}
|
|
1918
|
+
.planr-annotation-composer textarea { min-height: 84px; resize: vertical; }
|
|
1919
|
+
.planr-intent-picker { display: grid; grid-template-columns: repeat(3, 1fr); gap: 5px; margin-bottom: 9px; }
|
|
1920
|
+
.planr-intent-picker button {
|
|
1921
|
+
min-height: 30px;
|
|
1922
|
+
border: 1px solid var(--planr-color-rule);
|
|
1923
|
+
border-radius: var(--planr-radius-small);
|
|
1924
|
+
background: transparent;
|
|
1925
|
+
cursor: pointer;
|
|
1926
|
+
font-size: 10px;
|
|
1927
|
+
}
|
|
1928
|
+
.planr-intent-picker [aria-checked="true"] { border-color: var(--planr-color-primary); background: var(--planr-color-raised); }
|
|
1929
|
+
.planr-field-error { min-height: 16px; margin: 0; color: var(--planr-color-danger); font-size: 10px; }
|
|
1930
|
+
.planr-composer-actions { display: flex; justify-content: flex-end; gap: 7px; margin-top: 8px; }
|
|
1931
|
+
.planr-composer-actions button, .planr-thread button, .planr-reply-form button {
|
|
1932
|
+
min-height: 30px;
|
|
1933
|
+
padding: 0 10px;
|
|
1934
|
+
border: 1px solid var(--planr-color-rule);
|
|
1935
|
+
border-radius: var(--planr-radius-small);
|
|
1936
|
+
background: transparent;
|
|
1937
|
+
cursor: pointer;
|
|
1938
|
+
font-weight: 700;
|
|
1939
|
+
}
|
|
1940
|
+
.planr-composer-actions [type="submit"], .planr-reply-form [type="submit"] { border-color: var(--planr-color-primary); background: var(--planr-color-primary); color: var(--planr-color-background); }
|
|
1941
|
+
.planr-stage-status {
|
|
1942
|
+
position: absolute;
|
|
1943
|
+
z-index: 20;
|
|
1944
|
+
inset: 76px 0 0;
|
|
1945
|
+
display: grid;
|
|
1946
|
+
place-items: center;
|
|
1947
|
+
padding: 24px;
|
|
1948
|
+
background: color-mix(in srgb, var(--planr-color-stage) 94%, transparent);
|
|
1949
|
+
text-align: center;
|
|
1950
|
+
}
|
|
1951
|
+
.planr-stage-status > div {
|
|
1952
|
+
max-width: 400px;
|
|
1953
|
+
padding: 24px;
|
|
1954
|
+
border: 1px solid var(--planr-color-rule);
|
|
1955
|
+
border-radius: var(--planr-radius-large);
|
|
1956
|
+
background: var(--planr-color-chrome);
|
|
1957
|
+
box-shadow: 0 18px 60px color-mix(in srgb, var(--planr-color-background) 50%, transparent);
|
|
1958
|
+
}
|
|
1959
|
+
.planr-stage-status strong { display: block; margin-bottom: 7px; font: 650 18px/1.2 var(--planr-font-display); }
|
|
1960
|
+
.planr-stage-status p { margin: 0; color: var(--planr-color-text-muted); }
|
|
1961
|
+
.planr-review-rail {
|
|
1962
|
+
min-width: 0;
|
|
1963
|
+
display: grid;
|
|
1964
|
+
grid-template-rows: auto auto minmax(0, 1fr) auto;
|
|
1965
|
+
overflow: hidden;
|
|
1966
|
+
border-left: 1px solid var(--planr-color-rule);
|
|
1967
|
+
background: var(--planr-color-panel);
|
|
1968
|
+
transition: opacity var(--planr-motion-fast), transform var(--planr-motion-base);
|
|
1969
|
+
}
|
|
1970
|
+
.planr-domain-rail { max-height: 44vh; overflow: auto; padding: 12px 14px; border-top: 1px solid var(--planr-color-rule); }
|
|
1971
|
+
.planr-domain-rail[hidden] { display: none; }
|
|
1972
|
+
.planr-domain-rail h3 { margin: 0 0 8px; font: 650 12px/1.2 var(--planr-font-display); }
|
|
1973
|
+
.planr-domain-rail details { border-top: 1px solid var(--planr-color-rule); }
|
|
1974
|
+
.planr-domain-rail summary { padding: 9px 0; color: var(--planr-color-text); cursor: pointer; font-weight: 700; }
|
|
1975
|
+
.planr-domain-section-body { padding: 1px 0 10px; }
|
|
1976
|
+
.planr-domain-rail fieldset { margin: 0 0 9px; padding: 9px; border: 1px solid var(--planr-color-rule); border-radius: var(--planr-radius-small); }
|
|
1977
|
+
.planr-domain-rail legend { padding: 0 4px; color: var(--planr-color-text); font-size: 11px; font-weight: 700; }
|
|
1978
|
+
.planr-domain-rail label { display: grid; gap: 5px; margin-bottom: 8px; color: var(--planr-color-text-muted); font-size: 11px; }
|
|
1979
|
+
.planr-domain-rail input, .planr-domain-rail select, .planr-domain-rail textarea {
|
|
1980
|
+
width: 100%; border: 1px solid var(--planr-color-rule); border-radius: var(--planr-radius-small);
|
|
1981
|
+
background: var(--planr-color-background); color: var(--planr-color-text); padding: 7px 8px;
|
|
1982
|
+
}
|
|
1983
|
+
.planr-domain-actions { display: flex; flex-wrap: wrap; gap: 6px; }
|
|
1984
|
+
.planr-domain-remix { display: grid; grid-template-columns: 1fr 1fr; gap: 0 8px; }
|
|
1985
|
+
.planr-domain-remix label:last-child { grid-column: 1 / -1; }
|
|
1986
|
+
.planr-domain-section-body > p { margin: 8px 0 0; color: var(--planr-color-text-muted); font-size: 10px; }
|
|
1987
|
+
.planr-domain-actions button, .planr-domain-toolbar button {
|
|
1988
|
+
min-height: 28px; border: 1px solid var(--planr-color-rule); border-radius: var(--planr-radius-small);
|
|
1989
|
+
background: var(--planr-color-raised); color: var(--planr-color-text); padding: 0 9px; cursor: pointer;
|
|
1990
|
+
}
|
|
1991
|
+
.planr-shell[data-planr-rail-open="false"] .planr-review-rail { opacity: 0; pointer-events: none; }
|
|
1992
|
+
.planr-review-rail > header {
|
|
1993
|
+
min-height: 48px;
|
|
1994
|
+
display: flex;
|
|
1995
|
+
align-items: center;
|
|
1996
|
+
justify-content: space-between;
|
|
1997
|
+
padding: 0 14px;
|
|
1998
|
+
border-bottom: 1px solid var(--planr-color-rule);
|
|
1999
|
+
}
|
|
2000
|
+
.planr-review-rail h2 { margin: 0; font: 650 13px/1 var(--planr-font-display); }
|
|
2001
|
+
.planr-review-metrics { display: flex; align-items: center; gap: 6px; }
|
|
2002
|
+
.planr-review-header-actions { display: flex; align-items: center; gap: 8px; }
|
|
2003
|
+
.planr-rail-close {
|
|
2004
|
+
width: 32px;
|
|
2005
|
+
height: 32px;
|
|
2006
|
+
display: none;
|
|
2007
|
+
place-items: center;
|
|
2008
|
+
border: 1px solid var(--planr-color-rule);
|
|
2009
|
+
border-radius: var(--planr-radius-small);
|
|
2010
|
+
background: var(--planr-color-raised);
|
|
2011
|
+
color: var(--planr-color-text);
|
|
2012
|
+
font: 20px/1 var(--planr-font-body);
|
|
2013
|
+
cursor: pointer;
|
|
2014
|
+
}
|
|
2015
|
+
.planr-identity { padding: 10px 14px 0; border-bottom: 1px solid var(--planr-color-rule); }
|
|
2016
|
+
.planr-identity label { margin-bottom: 5px; }
|
|
2017
|
+
.planr-identity-status { min-height: 16px; margin: 0 0 8px !important; color: var(--planr-color-text-muted); font-size: 10px !important; }
|
|
2018
|
+
.planr-identity-status[data-planr-identity-ready="true"] { color: var(--planr-color-primary); }
|
|
2019
|
+
.planr-identity-status[data-planr-identity-ready="true"]::before { margin-right: 4px; content: '\u2713'; font-weight: 800; }
|
|
2020
|
+
.planr-feedback-slot { overflow: auto; padding: 14px; }
|
|
2021
|
+
.planr-feedback-slot > p {
|
|
2022
|
+
margin: 0;
|
|
2023
|
+
padding: 18px;
|
|
2024
|
+
border: 1px dashed var(--planr-color-rule);
|
|
2025
|
+
border-radius: var(--planr-radius-medium);
|
|
2026
|
+
color: var(--planr-color-text-muted);
|
|
2027
|
+
text-align: center;
|
|
2028
|
+
}
|
|
2029
|
+
.planr-thread-list { display: grid; gap: 10px; }
|
|
2030
|
+
.planr-review-empty {
|
|
2031
|
+
margin: 0;
|
|
2032
|
+
padding: 18px;
|
|
2033
|
+
border: 1px dashed var(--planr-color-rule);
|
|
2034
|
+
border-radius: var(--planr-radius-medium);
|
|
2035
|
+
color: var(--planr-color-text-muted);
|
|
2036
|
+
text-align: center;
|
|
2037
|
+
}
|
|
2038
|
+
.planr-thread {
|
|
2039
|
+
padding: 11px;
|
|
2040
|
+
border: 1px solid var(--planr-color-rule);
|
|
2041
|
+
border-left: 3px solid var(--planr-color-danger);
|
|
2042
|
+
border-radius: var(--planr-radius-medium);
|
|
2043
|
+
background: var(--planr-color-chrome);
|
|
2044
|
+
}
|
|
2045
|
+
.planr-thread[data-planr-intent="improve"] { border-left-color: var(--planr-color-primary-strong); }
|
|
2046
|
+
.planr-thread[data-planr-intent="question"] { border-left-color: var(--planr-color-question); }
|
|
2047
|
+
.planr-thread[data-planr-status="resolved"], .planr-thread[data-planr-status="addressed"] { border-left-color: var(--planr-color-resolved); opacity: .82; }
|
|
2048
|
+
.planr-thread > header { display: flex; align-items: center; gap: 7px; color: var(--planr-color-text-muted); font-size: 10px; }
|
|
2049
|
+
.planr-thread > header strong { color: var(--planr-color-text); font-size: 11px; }
|
|
2050
|
+
.planr-thread > header time { margin-left: auto; font-family: var(--planr-font-mono); }
|
|
2051
|
+
.planr-review-byline { display: flex; align-items: center; gap: 6px; }
|
|
2052
|
+
.planr-intent { color: var(--planr-color-text-muted); text-transform: capitalize; }
|
|
2053
|
+
.planr-thread-comment { margin: 9px 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
2054
|
+
.planr-thread-actions { display: flex; gap: 6px; }
|
|
2055
|
+
.planr-thread-actions button { min-height: 28px; padding-inline: 8px; font-size: 10px; }
|
|
2056
|
+
.planr-replies { display: grid; gap: 7px; margin: 10px 0 0 12px; padding-left: 10px; border-left: 1px solid var(--planr-color-rule); list-style: none; }
|
|
2057
|
+
.planr-reply { margin: 0; }
|
|
2058
|
+
.planr-reply header { display: flex; gap: 6px; color: var(--planr-color-text-muted); font-size: 9px; }
|
|
2059
|
+
.planr-reply header time { margin-left: auto; font-family: var(--planr-font-mono); }
|
|
2060
|
+
.planr-reply p { margin: 4px 0; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
2061
|
+
.planr-reply-editor { margin-top: 8px; }
|
|
2062
|
+
.planr-thread .planr-reply-toggle { min-height: 28px; padding: 2px 0; border-color: transparent; background: transparent; color: var(--planr-color-text-muted); font-size: 12px; font-weight: 550; }
|
|
2063
|
+
.planr-thread .planr-reply-toggle:hover, .planr-thread .planr-reply-toggle[aria-expanded="true"] { color: var(--planr-color-primary-strong); }
|
|
2064
|
+
.planr-reply-form { display: grid; gap: 6px; margin-top: 6px; }
|
|
2065
|
+
.planr-reply-form[hidden] { display: none; }
|
|
2066
|
+
.planr-reply-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
2067
|
+
.planr-reply-form textarea { min-height: 64px; max-height: 180px; resize: vertical; font: 13px/1.5 var(--planr-font-body); }
|
|
2068
|
+
.planr-reply-controls { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
|
|
2069
|
+
.planr-reply-hint { font-size: 11px; color: var(--planr-color-text-muted); }
|
|
2070
|
+
.planr-reply-form .planr-reply-send { display: grid; place-items: center; flex: 0 0 30px; width: 30px; min-width: 30px; height: 30px; min-height: 30px; padding: 0; border-radius: 7px; }
|
|
2071
|
+
.planr-reply-form .planr-reply-send:disabled { opacity: .35; cursor: not-allowed; }
|
|
2072
|
+
@media (max-width: 600px) { .planr-annotation-composer input, .planr-annotation-composer textarea, .planr-annotation-composer select { font-size: 16px; } }
|
|
2073
|
+
.planr-decision-slot { padding: 12px; border-top: 1px solid var(--planr-color-rule); background: var(--planr-color-chrome); }
|
|
2074
|
+
.planr-decision-slot label { display: block; margin-bottom: 6px; color: var(--planr-color-text-muted); font-size: 11px; }
|
|
2075
|
+
.planr-decision-slot textarea {
|
|
2076
|
+
width: 100%;
|
|
2077
|
+
min-height: 58px;
|
|
2078
|
+
resize: vertical;
|
|
2079
|
+
padding: 9px;
|
|
2080
|
+
border: 1px solid var(--planr-color-rule);
|
|
2081
|
+
border-radius: var(--planr-radius-small);
|
|
2082
|
+
background: var(--planr-color-panel);
|
|
2083
|
+
color: var(--planr-color-text);
|
|
2084
|
+
}
|
|
2085
|
+
.planr-decision-slot > div { display: grid; grid-template-columns: 1fr 1.2fr; gap: 8px; margin-top: 8px; }
|
|
2086
|
+
.planr-decision-slot button {
|
|
2087
|
+
min-height: 32px;
|
|
2088
|
+
border: 1px solid var(--planr-color-rule);
|
|
2089
|
+
border-radius: var(--planr-radius-small);
|
|
2090
|
+
background: transparent;
|
|
2091
|
+
cursor: pointer;
|
|
2092
|
+
font-weight: 700;
|
|
2093
|
+
}
|
|
2094
|
+
.planr-decision-slot button[data-planr-decision="approved"] { border-color: var(--planr-color-primary); color: var(--planr-color-primary); }
|
|
2095
|
+
.planr-decision-slot button[data-planr-decision="changes_requested"] { border-color: var(--planr-color-danger); background: var(--planr-color-danger); color: var(--planr-color-on-danger); }
|
|
2096
|
+
.planr-decision-slot button[aria-pressed="true"] { box-shadow: 0 0 0 2px color-mix(in srgb, currentColor 25%, transparent); }
|
|
2097
|
+
.planr-decision-slot > p { min-height: 16px; margin: 7px 0 0; color: var(--planr-color-text-muted); font-size: 10px; }
|
|
2098
|
+
[data-planr-slot="dialogs"] { position: fixed; z-index: 80; inset: 0; pointer-events: none; }
|
|
2099
|
+
.planr-dialog-backdrop {
|
|
2100
|
+
position: fixed;
|
|
2101
|
+
inset: 0;
|
|
2102
|
+
display: grid;
|
|
2103
|
+
place-items: center;
|
|
2104
|
+
padding: 20px;
|
|
2105
|
+
background: color-mix(in srgb, var(--planr-color-background) 76%, transparent);
|
|
2106
|
+
backdrop-filter: blur(8px);
|
|
2107
|
+
pointer-events: auto;
|
|
2108
|
+
}
|
|
2109
|
+
.planr-share-dialog {
|
|
2110
|
+
width: min(560px, 100%);
|
|
2111
|
+
max-height: calc(100vh - 40px);
|
|
2112
|
+
overflow: auto;
|
|
2113
|
+
border: 1px solid var(--planr-color-rule);
|
|
2114
|
+
border-radius: var(--planr-radius-large);
|
|
2115
|
+
background: var(--planr-color-chrome);
|
|
2116
|
+
box-shadow: 0 28px 100px color-mix(in srgb, var(--planr-color-background) 68%, transparent);
|
|
2117
|
+
animation: planr-dialog-in var(--planr-motion-base);
|
|
2118
|
+
}
|
|
2119
|
+
.planr-share-dialog > header {
|
|
2120
|
+
display: flex;
|
|
2121
|
+
align-items: flex-start;
|
|
2122
|
+
justify-content: space-between;
|
|
2123
|
+
gap: 18px;
|
|
2124
|
+
padding: 18px 18px 12px;
|
|
2125
|
+
}
|
|
2126
|
+
.planr-share-dialog h2 { margin: 0 0 4px; font: 650 18px/1.15 var(--planr-font-display); letter-spacing: -.025em; }
|
|
2127
|
+
.planr-share-dialog p { margin: 0; color: var(--planr-color-text-muted); font-size: 12px; }
|
|
2128
|
+
.planr-dialog-close {
|
|
2129
|
+
width: 32px;
|
|
2130
|
+
height: 32px;
|
|
2131
|
+
flex: none;
|
|
2132
|
+
border: 1px solid var(--planr-color-rule);
|
|
2133
|
+
border-radius: var(--planr-radius-small);
|
|
2134
|
+
background: transparent;
|
|
2135
|
+
color: var(--planr-color-text-muted);
|
|
2136
|
+
cursor: pointer;
|
|
2137
|
+
font-size: 18px;
|
|
2138
|
+
}
|
|
2139
|
+
.planr-share-receipt {
|
|
2140
|
+
margin: 8px 18px 10px;
|
|
2141
|
+
overflow: hidden;
|
|
2142
|
+
border: 1px solid var(--planr-color-rule);
|
|
2143
|
+
border-radius: var(--planr-radius-medium);
|
|
2144
|
+
background: var(--planr-color-panel);
|
|
2145
|
+
}
|
|
2146
|
+
.planr-share-receipt-row {
|
|
2147
|
+
width: 100%;
|
|
2148
|
+
min-height: 68px;
|
|
2149
|
+
display: grid;
|
|
2150
|
+
grid-template-columns: 30px minmax(0, 1fr) auto;
|
|
2151
|
+
align-items: center;
|
|
2152
|
+
gap: 10px;
|
|
2153
|
+
padding: 11px 12px;
|
|
2154
|
+
border: 0;
|
|
2155
|
+
border-bottom: 1px solid var(--planr-color-rule);
|
|
2156
|
+
background: transparent;
|
|
2157
|
+
color: var(--planr-color-text);
|
|
2158
|
+
text-align: left;
|
|
2159
|
+
cursor: pointer;
|
|
2160
|
+
}
|
|
2161
|
+
.planr-share-receipt-row:last-child { border-bottom: 0; }
|
|
2162
|
+
.planr-share-receipt-row.is-selected {
|
|
2163
|
+
background: color-mix(in srgb, var(--planr-color-primary) 8%, var(--planr-color-panel));
|
|
2164
|
+
box-shadow: inset 3px 0 var(--planr-color-primary);
|
|
2165
|
+
}
|
|
2166
|
+
.planr-share-receipt-row:disabled { opacity: .55; cursor: not-allowed; }
|
|
2167
|
+
.planr-share-receipt-row strong { display: block; margin-bottom: 3px; font-size: 12px; }
|
|
2168
|
+
.planr-share-receipt-row small { display: block; color: var(--planr-color-text-muted); font-size: 10.5px; }
|
|
2169
|
+
.planr-share-receipt-icon {
|
|
2170
|
+
width: 28px;
|
|
2171
|
+
height: 28px;
|
|
2172
|
+
display: grid;
|
|
2173
|
+
place-items: center;
|
|
2174
|
+
border-radius: var(--planr-radius-small);
|
|
2175
|
+
background: var(--planr-color-raised);
|
|
2176
|
+
font: 12px/1 var(--planr-font-mono);
|
|
2177
|
+
}
|
|
2178
|
+
.planr-share-receipt-size {
|
|
2179
|
+
color: var(--planr-color-primary);
|
|
2180
|
+
font: 9px/1.25 var(--planr-font-mono);
|
|
2181
|
+
letter-spacing: .04em;
|
|
2182
|
+
text-align: right;
|
|
2183
|
+
text-transform: uppercase;
|
|
2184
|
+
}
|
|
2185
|
+
.planr-share-threshold { padding: 0 18px; font: 10px/1.4 var(--planr-font-mono); }
|
|
2186
|
+
.planr-share-ttl { margin: 12px 18px 0; padding: 12px; border: 1px solid var(--planr-color-rule); border-radius: var(--planr-radius-medium); background: var(--planr-color-panel); }
|
|
2187
|
+
.planr-share-ttl label { display: flex; align-items: center; justify-content: space-between; gap: 12px; color: var(--planr-color-text); font-weight: 700; }
|
|
2188
|
+
.planr-share-ttl select {
|
|
2189
|
+
min-height: 34px;
|
|
2190
|
+
padding: 0 28px 0 9px;
|
|
2191
|
+
border: 1px solid var(--planr-color-rule);
|
|
2192
|
+
border-radius: var(--planr-radius-small);
|
|
2193
|
+
background: var(--planr-color-chrome);
|
|
2194
|
+
color: var(--planr-color-text);
|
|
2195
|
+
}
|
|
2196
|
+
.planr-share-ttl p { margin-top: 8px; font-size: 10.5px; }
|
|
2197
|
+
.planr-owner-custody { margin: 12px 18px 0; padding: 12px; border: 1px solid var(--planr-color-rule); border-radius: var(--planr-radius-medium); background: color-mix(in srgb, var(--planr-color-warning) 7%, var(--planr-color-panel)); }
|
|
2198
|
+
.planr-owner-custody strong, .planr-live-capability strong { display: block; margin-bottom: 3px; color: var(--planr-color-warning); }
|
|
2199
|
+
.planr-owner-custody p, .planr-live-capability p { margin: 4px 0 0; font-size: 10.5px; }
|
|
2200
|
+
.planr-share-result { margin: 12px 18px 0; padding: 12px; border: 1px solid var(--planr-color-primary); border-radius: var(--planr-radius-medium); background: color-mix(in srgb, var(--planr-color-primary) 6%, var(--planr-color-panel)); }
|
|
2201
|
+
.planr-share-result > label { display: block; margin-bottom: 6px; color: var(--planr-color-text-muted); font-size: 10px; }
|
|
2202
|
+
.planr-share-result > div { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; }
|
|
2203
|
+
.planr-share-result input {
|
|
2204
|
+
min-width: 0;
|
|
2205
|
+
padding: 8px 9px;
|
|
2206
|
+
border: 1px solid var(--planr-color-rule);
|
|
2207
|
+
border-radius: var(--planr-radius-small);
|
|
2208
|
+
background: var(--planr-color-chrome);
|
|
2209
|
+
color: var(--planr-color-text);
|
|
2210
|
+
font: 10px/1.3 var(--planr-font-mono);
|
|
2211
|
+
}
|
|
2212
|
+
.planr-live-capability { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--planr-color-rule); }
|
|
2213
|
+
.planr-live-capability > div { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 7px; margin-top: 7px; }
|
|
2214
|
+
.planr-share-result button, .planr-share-dialog footer button, .planr-hosted-viewer button {
|
|
2215
|
+
min-height: 34px;
|
|
2216
|
+
padding: 0 11px;
|
|
2217
|
+
border: 1px solid var(--planr-color-rule);
|
|
2218
|
+
border-radius: var(--planr-radius-small);
|
|
2219
|
+
background: transparent;
|
|
2220
|
+
color: var(--planr-color-text);
|
|
2221
|
+
cursor: pointer;
|
|
2222
|
+
font-weight: 700;
|
|
2223
|
+
}
|
|
2224
|
+
.planr-share-result button[data-planr-copy-state], .planr-toolbar-action[data-planr-copy-state] {
|
|
2225
|
+
min-width: 96px;
|
|
2226
|
+
transition: border-color var(--planr-motion-fast), background var(--planr-motion-fast), color var(--planr-motion-fast), transform var(--planr-motion-fast);
|
|
2227
|
+
}
|
|
2228
|
+
.planr-share-result button[data-planr-copy-state]::before, .planr-toolbar-action[data-planr-copy-state]::before {
|
|
2229
|
+
display: inline-grid;
|
|
2230
|
+
width: 17px;
|
|
2231
|
+
height: 17px;
|
|
2232
|
+
place-items: center;
|
|
2233
|
+
margin-right: 6px;
|
|
2234
|
+
border-radius: 50%;
|
|
2235
|
+
content: '';
|
|
2236
|
+
font: 800 11px/1 var(--planr-font-mono);
|
|
2237
|
+
vertical-align: -1px;
|
|
2238
|
+
}
|
|
2239
|
+
.planr-share-result button[data-planr-copy-state="copied"], .planr-toolbar-action[data-planr-copy-state="copied"] {
|
|
2240
|
+
border-color: var(--planr-color-primary);
|
|
2241
|
+
background: color-mix(in srgb, var(--planr-color-primary) 12%, transparent);
|
|
2242
|
+
color: var(--planr-color-primary-strong);
|
|
2243
|
+
animation: planr-copy-confirm var(--planr-motion-base);
|
|
2244
|
+
}
|
|
2245
|
+
.planr-share-result button[data-planr-copy-state="copied"]::before, .planr-toolbar-action[data-planr-copy-state="copied"]::before { background: var(--planr-color-primary); color: var(--planr-color-background); content: '\u2713'; }
|
|
2246
|
+
.planr-share-result button[data-planr-copy-state="error"], .planr-toolbar-action[data-planr-copy-state="error"] { border-color: var(--planr-color-danger); color: var(--planr-color-danger); }
|
|
2247
|
+
.planr-share-result button[data-planr-copy-state="error"]::before, .planr-toolbar-action[data-planr-copy-state="error"]::before { border: 1px solid currentColor; content: '!'; }
|
|
2248
|
+
.planr-deletion-receipt { margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--planr-color-rule); }
|
|
2249
|
+
.planr-deletion-receipt strong { display: block; margin-bottom: 3px; color: var(--planr-color-warning); }
|
|
2250
|
+
.planr-deletion-receipt code { display: block; margin: 9px 0; padding: 9px; overflow-wrap: anywhere; border-radius: var(--planr-radius-small); background: var(--planr-color-background); color: var(--planr-color-text); }
|
|
2251
|
+
.planr-share-error { margin: 10px 18px 0 !important; color: var(--planr-color-danger) !important; }
|
|
2252
|
+
.planr-share-dialog > footer { display: flex; justify-content: flex-end; gap: 8px; padding: 14px 18px 18px; }
|
|
2253
|
+
.planr-share-dialog footer .planr-share-confirm { border-color: var(--planr-color-primary); background: var(--planr-color-primary); color: var(--planr-color-background); }
|
|
2254
|
+
.planr-share-dialog footer button:disabled { opacity: .55; cursor: wait; }
|
|
2255
|
+
.planr-hosted-viewer {
|
|
2256
|
+
position: fixed;
|
|
2257
|
+
z-index: 70;
|
|
2258
|
+
inset: var(--planr-toolbar-height) 0 0;
|
|
2259
|
+
display: grid;
|
|
2260
|
+
place-items: center;
|
|
2261
|
+
padding: 24px;
|
|
2262
|
+
background: var(--planr-color-stage);
|
|
2263
|
+
}
|
|
2264
|
+
.planr-hosted-viewer > div {
|
|
2265
|
+
width: min(420px, 100%);
|
|
2266
|
+
padding: 28px;
|
|
2267
|
+
border: 1px solid var(--planr-color-rule);
|
|
2268
|
+
border-radius: var(--planr-radius-large);
|
|
2269
|
+
background: var(--planr-color-chrome);
|
|
2270
|
+
text-align: center;
|
|
2271
|
+
box-shadow: 0 18px 60px color-mix(in srgb, var(--planr-color-background) 50%, transparent);
|
|
2272
|
+
}
|
|
2273
|
+
.planr-hosted-viewer .planr-mark { width: 40px; height: 40px; margin-bottom: 20px; }
|
|
2274
|
+
.planr-hosted-viewer strong { display: block; margin-bottom: 7px; font: 650 19px/1.2 var(--planr-font-display); }
|
|
2275
|
+
.planr-hosted-viewer p { margin: 0; color: var(--planr-color-text-muted); }
|
|
2276
|
+
.planr-hosted-viewer button { margin-top: 16px; border-color: var(--planr-color-primary); color: var(--planr-color-primary); }
|
|
2277
|
+
@keyframes planr-dialog-in { from { opacity: 0; transform: translateY(8px) scale(.985); } }
|
|
2278
|
+
@keyframes planr-copy-confirm { 50% { transform: translateY(-1px); } }
|
|
2279
|
+
@media (max-width: 900px) {
|
|
2280
|
+
.planr-title-block > span, .planr-privacy, .planr-toolbar .planr-segment:first-of-type { display: none; }
|
|
2281
|
+
.planr-title-block strong { max-width: 170px; }
|
|
2282
|
+
.planr-workspace, .planr-shell[data-planr-rail-open="false"] .planr-workspace { grid-template-columns: minmax(0, 1fr); }
|
|
2283
|
+
.planr-review-rail {
|
|
2284
|
+
position: fixed;
|
|
2285
|
+
z-index: 50;
|
|
2286
|
+
inset: auto 0 0;
|
|
2287
|
+
height: min(52vh, 480px);
|
|
2288
|
+
height: min(52dvh, 480px);
|
|
2289
|
+
max-height: calc(100dvh - var(--planr-toolbar-height));
|
|
2290
|
+
padding-bottom: env(safe-area-inset-bottom, 0px);
|
|
2291
|
+
overscroll-behavior: contain;
|
|
2292
|
+
border: 1px solid var(--planr-color-rule);
|
|
2293
|
+
border-radius: var(--planr-radius-large) var(--planr-radius-large) 0 0;
|
|
2294
|
+
box-shadow: 0 18px 60px color-mix(in srgb, var(--planr-color-background) 52%, transparent);
|
|
2295
|
+
}
|
|
2296
|
+
.planr-shell[data-planr-rail-open="false"] .planr-review-rail { opacity: 0; visibility: hidden; transform: translate3d(0, calc(100% + env(safe-area-inset-bottom, 0px)), 0); }
|
|
2297
|
+
.planr-rail-close { display: grid; }
|
|
2298
|
+
.planr-stage-scroll { padding-left: 14px; }
|
|
2299
|
+
}
|
|
2300
|
+
@media (max-width: 620px) {
|
|
2301
|
+
.planr-toolbar {
|
|
2302
|
+
gap: 5px;
|
|
2303
|
+
padding-left: max(8px, env(safe-area-inset-left));
|
|
2304
|
+
padding-right: max(8px, env(safe-area-inset-right));
|
|
2305
|
+
}
|
|
2306
|
+
.planr-brand { max-width: 128px; }
|
|
2307
|
+
.planr-title-block strong { max-width: 96px; }
|
|
2308
|
+
.planr-toolbar [data-planr-mode] { min-width: 42px; padding-inline: 7px; }
|
|
2309
|
+
.planr-toolbar [data-planr-mode="interact"] { font-size: 0; }
|
|
2310
|
+
.planr-toolbar [data-planr-mode="interact"]::before { content: "View"; font-size: 11px; }
|
|
2311
|
+
.planr-toolbar-action { padding-inline: 8px; }
|
|
2312
|
+
.planr-stage-controls { padding-inline: 10px; }
|
|
2313
|
+
.planr-dialog-backdrop { align-items: end; padding: 0; }
|
|
2314
|
+
.planr-share-dialog { width: 100%; max-height: min(78vh, 680px); border-radius: var(--planr-radius-large) var(--planr-radius-large) 0 0; }
|
|
2315
|
+
}
|
|
2316
|
+
@media (max-width: 390px) {
|
|
2317
|
+
.planr-toolbar { gap: 5px; padding-inline: 8px; }
|
|
2318
|
+
.planr-brand { max-width: 20px; }
|
|
2319
|
+
.planr-title-block { display: none; }
|
|
2320
|
+
.planr-toolbar-action[data-planr-action="feedback"] { min-width: 32px; padding-inline: 6px; }
|
|
2321
|
+
.planr-toolbar-action[data-planr-action="feedback"] .planr-feedback-label { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
|
|
2322
|
+
.planr-variants button { max-width: 150px; padding-inline: 8px; }
|
|
2323
|
+
.planr-share-receipt-row { grid-template-columns: 28px minmax(0, 1fr); }
|
|
2324
|
+
.planr-share-receipt-size { grid-column: 2; text-align: left; }
|
|
2325
|
+
.planr-share-result > div { grid-template-columns: 1fr; }
|
|
2326
|
+
}
|
|
2327
|
+
/* Document presentation: the artifact owns the page; the iframe is only an
|
|
2328
|
+
opaque-origin security boundary and has no visible canvas treatment. */
|
|
2329
|
+
html[data-planr-presentation="document"],
|
|
2330
|
+
html[data-planr-presentation="document"] body {
|
|
2331
|
+
height: auto;
|
|
2332
|
+
min-height: 100%;
|
|
2333
|
+
}
|
|
2334
|
+
html[data-planr-presentation="document"] body { overflow: visible; }
|
|
2335
|
+
.planr-shell[data-planr-presentation="document"] {
|
|
2336
|
+
height: auto;
|
|
2337
|
+
min-height: 100vh;
|
|
2338
|
+
display: block;
|
|
2339
|
+
}
|
|
2340
|
+
.planr-shell[data-planr-presentation="document"] .planr-workspace,
|
|
2341
|
+
.planr-shell[data-planr-presentation="document"][data-planr-rail-open="false"] .planr-workspace {
|
|
2342
|
+
display: block;
|
|
2343
|
+
}
|
|
2344
|
+
.planr-shell[data-planr-presentation="document"] .planr-stage {
|
|
2345
|
+
overflow: visible;
|
|
2346
|
+
background: transparent;
|
|
2347
|
+
}
|
|
2348
|
+
.planr-shell[data-planr-presentation="document"] .planr-stage-scroll {
|
|
2349
|
+
position: static;
|
|
2350
|
+
overflow: visible;
|
|
2351
|
+
padding: 0;
|
|
2352
|
+
}
|
|
2353
|
+
.planr-shell[data-planr-presentation="document"] .planr-stage-surface {
|
|
2354
|
+
width: 100%;
|
|
2355
|
+
min-width: 100%;
|
|
2356
|
+
transform: none;
|
|
2357
|
+
}
|
|
2358
|
+
.planr-shell[data-planr-presentation="document"] .planr-frame-grid {
|
|
2359
|
+
width: 100%;
|
|
2360
|
+
display: block;
|
|
2361
|
+
}
|
|
2362
|
+
.planr-shell[data-planr-presentation="document"] .planr-artifact-panel {
|
|
2363
|
+
width: 100%;
|
|
2364
|
+
max-width: 100%;
|
|
2365
|
+
min-width: 0;
|
|
2366
|
+
height: var(--planr-document-height, max(var(--planr-artifact-height), 100vh));
|
|
2367
|
+
min-height: 100vh;
|
|
2368
|
+
}
|
|
2369
|
+
.planr-shell[data-planr-presentation="document"] .planr-frame {
|
|
2370
|
+
overflow: visible;
|
|
2371
|
+
border-radius: 0;
|
|
2372
|
+
background: transparent;
|
|
2373
|
+
box-shadow: none;
|
|
2374
|
+
}
|
|
2375
|
+
.planr-shell[data-planr-presentation="document"] .planr-frame iframe {
|
|
2376
|
+
width: 100%;
|
|
2377
|
+
max-width: 100%;
|
|
2378
|
+
background: transparent;
|
|
2379
|
+
}
|
|
2380
|
+
.planr-shell[data-planr-presentation="document"] .planr-frame-label { display: none; }
|
|
2381
|
+
.planr-shell[data-planr-presentation="document"] .planr-stage-status { inset: 0; min-height: 100vh; }
|
|
2382
|
+
.planr-shell[data-planr-presentation="document"] .planr-review-rail {
|
|
2383
|
+
position: fixed;
|
|
2384
|
+
z-index: 50;
|
|
2385
|
+
inset: 0 0 0 auto;
|
|
2386
|
+
width: min(var(--planr-review-rail-width), 100vw);
|
|
2387
|
+
border-left: 1px solid var(--planr-color-rule);
|
|
2388
|
+
box-shadow: -18px 0 60px color-mix(in srgb, var(--planr-color-background) 34%, transparent);
|
|
2389
|
+
}
|
|
2390
|
+
.planr-shell[data-planr-presentation="document"][data-planr-rail-open="false"] .planr-review-rail {
|
|
2391
|
+
opacity: 1;
|
|
2392
|
+
transform: translateX(100%);
|
|
2393
|
+
}
|
|
2394
|
+
@media (max-width: 900px) {
|
|
2395
|
+
.planr-shell[data-planr-presentation="document"] .planr-floating-actions {
|
|
2396
|
+
top: auto;
|
|
2397
|
+
right: max(12px, env(safe-area-inset-right));
|
|
2398
|
+
bottom: max(12px, env(safe-area-inset-bottom));
|
|
2399
|
+
flex-direction: row;
|
|
2400
|
+
transform: none;
|
|
2401
|
+
}
|
|
2402
|
+
.planr-shell[data-planr-presentation="document"] .planr-floating-actions > button,
|
|
2403
|
+
.planr-shell[data-planr-presentation="document"] .planr-floating-actions > .planr-more > button {
|
|
2404
|
+
width: 44px;
|
|
2405
|
+
height: 44px;
|
|
2406
|
+
}
|
|
2407
|
+
.planr-shell[data-planr-presentation="document"] .planr-floating-actions [data-planr-tooltip]::after { display: none; }
|
|
2408
|
+
.planr-shell[data-planr-presentation="document"] .planr-more-menu {
|
|
2409
|
+
right: 0;
|
|
2410
|
+
bottom: calc(100% + 10px);
|
|
2411
|
+
}
|
|
2412
|
+
.planr-shell[data-planr-presentation="document"] .planr-review-rail {
|
|
2413
|
+
inset: auto 0 0;
|
|
2414
|
+
width: 100%;
|
|
2415
|
+
max-height: 100dvh;
|
|
2416
|
+
}
|
|
2417
|
+
.planr-shell[data-planr-presentation="document"][data-planr-rail-open="false"] .planr-review-rail {
|
|
2418
|
+
transform: translate3d(0, calc(100% + env(safe-area-inset-bottom, 0px)), 0);
|
|
2419
|
+
}
|
|
2420
|
+
}
|
|
2421
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2422
|
+
*, *::before, *::after {
|
|
2423
|
+
animation-duration: 0s !important;
|
|
2424
|
+
animation-iteration-count: 1 !important;
|
|
2425
|
+
scroll-behavior: auto !important;
|
|
2426
|
+
transition-duration: 0s !important;
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
`;
|
|
2430
|
+
var DEFAULT_ARTIFACT_SHELL_INPUT = Object.freeze({
|
|
2431
|
+
envelope: Object.freeze({ artifacts: Object.freeze([]) }),
|
|
2432
|
+
viewer: Object.freeze({ mode: "single", status: "loading" }),
|
|
2433
|
+
shell: Object.freeze({
|
|
2434
|
+
title: "OpenPlanr artifact review",
|
|
2435
|
+
privacy: "local",
|
|
2436
|
+
theme: "auto",
|
|
2437
|
+
railOpen: false,
|
|
2438
|
+
feedbackCount: 0,
|
|
2439
|
+
zoom: 72
|
|
2440
|
+
})
|
|
2441
|
+
});
|
|
2442
|
+
function renderArtifactShellDocument(input = DEFAULT_ARTIFACT_SHELL_INPUT, { theme, stageRuntimeUrl = "./artifact-review-stage.js" } = {}) {
|
|
2443
|
+
const model = normalizeArtifactShellModel(input);
|
|
2444
|
+
const canonicalTheme = theme ?? loadArtifactTheme();
|
|
2445
|
+
const themeCss = renderArtifactThemeCss(canonicalTheme);
|
|
2446
|
+
const shellMarkup = renderArtifactShellMarkup(model);
|
|
2447
|
+
const modelData = renderArtifactShellModelData(model);
|
|
2448
|
+
const stagePayload = createArtifactStagePayload(input.envelope, {
|
|
2449
|
+
viewer: input.viewer ?? input.envelope?.viewer
|
|
2450
|
+
});
|
|
2451
|
+
const reviewState = Object.freeze({
|
|
2452
|
+
schemaVersion: "1.0.0",
|
|
2453
|
+
reviewOf: digestArtifactEnvelope({
|
|
2454
|
+
schemaVersion: input.envelope?.schemaVersion ?? "1.0.0",
|
|
2455
|
+
artifacts: input.envelope?.artifacts ?? [],
|
|
2456
|
+
viewer: input.envelope?.viewer ?? input.viewer ?? {}
|
|
2457
|
+
}),
|
|
2458
|
+
review: input.envelope?.review ?? null
|
|
2459
|
+
});
|
|
2460
|
+
return `<!doctype html>
|
|
2461
|
+
<html lang="en" data-planr-theme="${model.theme}" data-planr-presentation="${model.presentation}" data-planr-artifact-shell="${ARTIFACT_SHELL_VERSION}">
|
|
2462
|
+
<head>
|
|
2463
|
+
<meta charset="utf-8">
|
|
2464
|
+
<meta name="viewport" content="width=device-width,initial-scale=1,viewport-fit=cover">
|
|
2465
|
+
<meta name="robots" content="noindex,nofollow,noarchive">
|
|
2466
|
+
<meta name="referrer" content="no-referrer">
|
|
2467
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline' data: blob:; script-src 'self' 'unsafe-inline' data: blob:; frame-src blob:; img-src data: blob:; media-src data: blob:; font-src data:; connect-src 'self'; worker-src data: blob:; object-src 'none'; form-action 'none'; base-uri 'none'">
|
|
2468
|
+
<title>${escapeHtml(model.title)} \xB7 OpenPlanr</title>
|
|
2469
|
+
<style>
|
|
2470
|
+
${themeCss}${ARTIFACT_SHELL_CSS}</style>
|
|
2471
|
+
</head>
|
|
2472
|
+
<body>
|
|
2473
|
+
${shellMarkup}
|
|
2474
|
+
<script type="application/json" id="planr-artifact-shell-model">${modelData}</script>
|
|
2475
|
+
<script type="application/json" id="planr-artifact-stage-payload">${embedJson(stagePayload)}</script>
|
|
2476
|
+
<script type="application/json" id="planr-artifact-review-state">${embedJson(reviewState)}</script>
|
|
2477
|
+
<script src="${escapeHtml(stageRuntimeUrl)}" defer></script>
|
|
2478
|
+
</body>
|
|
2479
|
+
</html>
|
|
2480
|
+
`;
|
|
2481
|
+
}
|
|
2482
|
+
function renderArtifactShellTemplate({ theme } = {}) {
|
|
2483
|
+
return renderArtifactShellDocument(DEFAULT_ARTIFACT_SHELL_INPUT, { theme });
|
|
2484
|
+
}
|
|
2485
|
+
|
|
2486
|
+
export {
|
|
2487
|
+
ARTIFACT_ERROR_CODES,
|
|
2488
|
+
PipelineError,
|
|
2489
|
+
validateJson,
|
|
2490
|
+
canonicalSerialize,
|
|
2491
|
+
digestArtifactEnvelope,
|
|
2492
|
+
validateArtifactReview,
|
|
2493
|
+
validateArtifactEnvelope,
|
|
2494
|
+
createArtifactEnvelope,
|
|
2495
|
+
contrastRatio,
|
|
2496
|
+
AA_NORMAL,
|
|
2497
|
+
escapeHtml,
|
|
2498
|
+
embedJson,
|
|
2499
|
+
renderPlanrMark,
|
|
2500
|
+
ARTIFACT_SHELL_VERSION,
|
|
2501
|
+
ARTIFACT_SHELL_ASSET_PATHS,
|
|
2502
|
+
ARTIFACT_SHELL_CSS,
|
|
2503
|
+
DEFAULT_ARTIFACT_SHELL_INPUT,
|
|
2504
|
+
renderArtifactShellDocument,
|
|
2505
|
+
renderArtifactShellTemplate
|
|
2506
|
+
};
|