openplanr 2.4.0 → 2.6.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 +19 -0
- package/dist/dashboard/.vite/manifest.json +1 -1
- package/dist/dashboard/assets/{index-B74ddyXA.js → index-BaARCc1f.js} +2 -2
- package/dist/dashboard/assets/{index-B74ddyXA.js.map → index-BaARCc1f.js.map} +1 -1
- package/dist/dashboard/dashboard-manifest.json +8 -8
- package/dist/dashboard/index.html +1 -1
- 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/plugin.json +1 -1
- package/lib/host-packages/claude/openplanr/.openplanr-content.json +114 -26
- package/lib/host-packages/claude/openplanr/skills/design/SKILL.md +3 -0
- package/lib/host-packages/claude/openplanr/skills/design/openplanr.skill.json +56 -1
- package/lib/host-packages/claude/openplanr/skills/design/references/handoff.md +35 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/design.mjs +2285 -341
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/claude/openplanr/skills/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/openplanr.skill.json +55 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/references/handoff.md +35 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/design.mjs +2285 -341
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/claude/openplanr/skills/design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/SKILL.md +3 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/openplanr.skill.json +56 -1
- package/lib/host-packages/claude/openplanr/skills/design-review/references/handoff.md +35 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/design.mjs +2285 -341
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/claude/openplanr/skills/design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/claude/openplanr/skills/plan/SKILL.md +12 -0
- package/lib/host-packages/claude/openplanr/skills/plan/openplanr.skill.json +67 -1
- package/lib/host-packages/claude/openplanr/skills/plan/references/design-handoff.md +33 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/design.mjs +2285 -341
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/claude/openplanr/skills/plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/claude/openplanr/skills/ship/SKILL.md +9 -0
- package/lib/host-packages/claude/openplanr/skills/ship/openplanr.skill.json +12 -1
- package/lib/host-packages/claude/openplanr/skills/ship/references/design-lineage.md +26 -0
- package/lib/host-packages/cursor/openplanr/.openplanr-content.json +109 -21
- package/lib/host-packages/cursor/openplanr/manifest.json +1 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design/references/handoff.md +35 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/design.mjs +2285 -341
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/references/handoff.md +35 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/design.mjs +2285 -341
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/references/handoff.md +35 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/design.mjs +2285 -341
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design-review.mdc +3 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-design.mdc +3 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/references/design-handoff.md +33 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/design.mjs +2285 -341
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-plan.mdc +12 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-ship/references/design-lineage.md +26 -0
- package/lib/host-packages/cursor/openplanr/rules/planr-ship.mdc +9 -0
- package/lib/host-packages/openai/.claude-plugin/marketplace.json +2 -2
- package/lib/host-packages/openai/openplanr/.codex-plugin/plugin.json +1 -1
- package/lib/host-packages/openai/openplanr/.openplanr-content.json +114 -26
- package/lib/host-packages/openai/openplanr/skills/design/SKILL.md +3 -0
- package/lib/host-packages/openai/openplanr/skills/design/openplanr.skill.json +56 -1
- package/lib/host-packages/openai/openplanr/skills/design/references/handoff.md +35 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/design.mjs +2285 -341
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/openai/openplanr/skills/design/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/openplanr.skill.json +55 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/references/handoff.md +35 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/design.mjs +2285 -341
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/openai/openplanr/skills/design-loop/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/SKILL.md +3 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/openplanr.skill.json +56 -1
- package/lib/host-packages/openai/openplanr/skills/design-review/references/handoff.md +35 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/design.mjs +2285 -341
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/openai/openplanr/skills/design-review/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/openai/openplanr/skills/plan/SKILL.md +12 -0
- package/lib/host-packages/openai/openplanr/skills/plan/openplanr.skill.json +67 -1
- package/lib/host-packages/openai/openplanr/skills/plan/references/design-handoff.md +33 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/design.mjs +2285 -341
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/enhancements.js +19 -2
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/handoff-center.css +81 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/design/templates/studio/handoff-center.js +400 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/protocol/package.json +5 -1
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-handoff-readiness.schema.json +398 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-implementation-handoff.schema.json +518 -0
- package/lib/host-packages/openai/openplanr/skills/plan/scripts/runtime/packages/protocol/schemas/v1.11.0/design-planning-lineage.schema.json +106 -0
- package/lib/host-packages/openai/openplanr/skills/ship/SKILL.md +9 -0
- package/lib/host-packages/openai/openplanr/skills/ship/openplanr.skill.json +12 -1
- package/lib/host-packages/openai/openplanr/skills/ship/references/design-lineage.md +26 -0
- package/package.json +2 -2
|
@@ -736,7 +736,7 @@
|
|
|
736
736
|
await loadPair();
|
|
737
737
|
} catch (error) { status.textContent = error.message; content.append(button('Retry comparison', () => compareRevisions(beforeId, afterId))); }
|
|
738
738
|
}
|
|
739
|
-
async function
|
|
739
|
+
async function reviewHandoff() {
|
|
740
740
|
const { content, status } = modal('Review handoff', 'design-handoff'); status.textContent = 'Loading review decisions…'; let response;
|
|
741
741
|
const render = () => {
|
|
742
742
|
content.replaceChildren(); const draft = response.draft;
|
|
@@ -766,7 +766,11 @@
|
|
|
766
766
|
catch (error) { status.textContent = error.message; for (const [value,disabled] of previousDisabled) if (value.isConnected) value.disabled = disabled; }
|
|
767
767
|
};
|
|
768
768
|
try { response = await request('loadHandoff', 'handoffUrl'); if (response.metadata) metadata = response.metadata; render(); status.textContent = ''; }
|
|
769
|
-
catch (error) { status.textContent = error.message; content.append(button('Retry',
|
|
769
|
+
catch (error) { status.textContent = error.message; content.append(button('Retry', reviewHandoff)); }
|
|
770
|
+
}
|
|
771
|
+
function handoff() {
|
|
772
|
+
if (window.__openPlanrDesignHandoffCenter?.open) return window.__openPlanrDesignHandoffCenter.open();
|
|
773
|
+
return reviewHandoff();
|
|
770
774
|
}
|
|
771
775
|
async function refreshExperience() {
|
|
772
776
|
const previousContext = JSON.stringify(experience?.reviewContext || payload.reviewContext);
|
|
@@ -797,6 +801,19 @@
|
|
|
797
801
|
on(document, 'openplanr:design-experience-changed', () => { void refreshExperience(); });
|
|
798
802
|
const dispose = () => { disposed = true; activeDialog?.dismiss(); for (const remove of destroyers.splice(0)) remove(); clearTimeout(toastTimer); };
|
|
799
803
|
on(window, 'pagehide', dispose); on(root, 'planr:design-destroy', dispose);
|
|
804
|
+
window.__openPlanrDesignHandoffBridge = Object.freeze({
|
|
805
|
+
owner,
|
|
806
|
+
revision,
|
|
807
|
+
announce,
|
|
808
|
+
openReviewHandoff: reviewHandoff,
|
|
809
|
+
loadReadiness: () => request('loadReadiness', 'readinessUrl'),
|
|
810
|
+
loadReviewHandoff: () => request('loadHandoff', 'handoffUrl'),
|
|
811
|
+
updateReviewHandoff: input => request('updateHandoff', 'handoffUrl', { ...input, revision: revision() }),
|
|
812
|
+
loadImplementationHandoff: () => request('loadImplementationHandoff', 'implementationHandoffUrl'),
|
|
813
|
+
updateImplementationHandoff: input => request('updateImplementationHandoff', 'implementationHandoffUrl', input),
|
|
814
|
+
continueToActiveHost: typeof options().continueToPlan === 'function' ? handoff => options().continueToPlan(handoff) : null,
|
|
815
|
+
context: () => ({ designId: design.id, title: design.title, revision: revision(), host: options().activeHost || options().host || '' }),
|
|
816
|
+
});
|
|
800
817
|
window.__openPlanrDesignExperience = Object.freeze({ about, profile:editProfile, setTheme, history, compare: compareRevisions, handoff, refresh: refreshExperience, getState: () => ({ railTab, inspectorEnabled, profile:{...profile}, theme:themePreference, metadata: structuredClone(metadata) }) });
|
|
801
818
|
}
|
|
802
819
|
const started = Date.now();
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
.design-handoff-center {
|
|
2
|
+
position:absolute; z-index:42; inset:var(--planr-toolbar-height) 0 0 auto;
|
|
3
|
+
display:grid; grid-template-rows:auto auto minmax(0,1fr) auto;
|
|
4
|
+
box-sizing:border-box; width:var(--planr-review-rail-width); max-width:calc(100% - 16px);
|
|
5
|
+
border-left:1px solid var(--planr-color-rule); background:var(--planr-color-chrome);
|
|
6
|
+
color:var(--planr-color-text); box-shadow:-12px 0 32px #0004;
|
|
7
|
+
font:400 var(--design-type-body,14px)/1.5 var(--planr-font-body);
|
|
8
|
+
}
|
|
9
|
+
.design-handoff-center > header { display:flex; align-items:center; justify-content:space-between; gap:12px; min-height:64px; padding:12px 14px; border-bottom:1px solid var(--planr-color-rule); }
|
|
10
|
+
.design-handoff-center h2,.design-handoff-center h3,.design-handoff-center h4 { margin:0; font-family:var(--planr-font-display); }
|
|
11
|
+
.design-handoff-center h2 { font-size:18px; }
|
|
12
|
+
.design-handoff-center h3 { font-size:20px; letter-spacing:-.02em; }
|
|
13
|
+
.design-handoff-center h4 { font-size:14px; }
|
|
14
|
+
.design-handoff-center p { margin:0; overflow-wrap:anywhere; }
|
|
15
|
+
.design-handoff-eyebrow { display:block; color:var(--planr-color-primary); font-size:10px; font-weight:650; letter-spacing:.1em; text-transform:uppercase; }
|
|
16
|
+
.design-handoff-center button,.design-handoff-center input,.design-handoff-center textarea { box-sizing:border-box; font:inherit; }
|
|
17
|
+
.design-handoff-center button { min-height:36px; border:1px solid var(--planr-color-rule); border-radius:6px; padding:8px 10px; color:var(--planr-color-text); background:var(--planr-color-panel); cursor:pointer; }
|
|
18
|
+
.design-handoff-center button:disabled { cursor:wait; opacity:.55; }
|
|
19
|
+
.design-handoff-center button:focus-visible,.design-handoff-center input:focus-visible,.design-handoff-center textarea:focus-visible,.design-handoff-center summary:focus-visible { outline:2px solid var(--planr-color-primary); outline-offset:2px; }
|
|
20
|
+
.design-handoff-center .design-primary { width:100%; min-height:44px; color:var(--planr-color-background); background:var(--planr-color-primary); border-color:transparent; font-weight:650; }
|
|
21
|
+
.design-handoff-center .design-danger { width:100%; color:var(--planr-color-danger); }
|
|
22
|
+
.design-handoff-close { width:44px; height:44px; padding:0!important; background:transparent!important; font-size:22px!important; }
|
|
23
|
+
.design-handoff-progress { display:grid; grid-template-columns:repeat(5,minmax(0,1fr)); gap:2px; padding:8px; border-bottom:1px solid var(--planr-color-rule); overflow-x:auto; }
|
|
24
|
+
.design-handoff-step { display:grid; justify-items:center; gap:3px; min-width:52px; min-height:58px!important; padding:6px 3px!important; border-color:transparent!important; background:transparent!important; color:var(--planr-color-text-muted)!important; font-size:9px!important; line-height:1.2!important; text-align:center; }
|
|
25
|
+
.design-handoff-step > span:first-child { font:650 11px/1 var(--planr-font-body); }
|
|
26
|
+
.design-handoff-step[aria-current="step"] { color:var(--planr-color-text)!important; background:var(--planr-color-raised)!important; border-color:var(--planr-color-rule)!important; }
|
|
27
|
+
.design-handoff-badge { display:inline-flex; align-items:center; gap:4px; width:max-content; max-width:100%; border:1px solid var(--planr-color-rule); border-radius:999px; padding:2px 7px; color:var(--planr-color-text-muted); font-size:10px; font-weight:650; line-height:1.4; }
|
|
28
|
+
.design-handoff-badge::before { content:""; flex:none; width:6px; height:6px; border-radius:50%; background:currentColor; }
|
|
29
|
+
.design-handoff-badge[data-status="ready"],.design-handoff-badge[data-status="pass"],.design-handoff-badge[data-status="approved"] { color:var(--planr-color-primary); }
|
|
30
|
+
.design-handoff-badge[data-status="blocked"],.design-handoff-badge[data-status="revoked"] { color:var(--planr-color-danger); }
|
|
31
|
+
.design-handoff-badge[data-status="attention"],.design-handoff-badge[data-status="stale"],.design-handoff-badge[data-status="superseded"] { color:#d89a39; }
|
|
32
|
+
.design-handoff-step .design-handoff-badge { max-width:44px; overflow:hidden; padding:0; border:0; color:inherit; text-indent:-999px; }
|
|
33
|
+
.design-handoff-step .design-handoff-badge::before { width:7px; height:7px; }
|
|
34
|
+
.design-handoff-content { min-width:0; overflow:auto; overscroll-behavior:contain; padding:18px 14px 28px; }
|
|
35
|
+
.design-handoff-step-header { display:grid; gap:6px; margin-bottom:18px; }
|
|
36
|
+
.design-handoff-step-header p,.design-handoff-content .design-muted { color:var(--planr-color-text-muted); font-size:12px; }
|
|
37
|
+
.design-handoff-summary { display:grid; gap:10px; margin-bottom:16px; padding:12px; border:1px solid var(--planr-color-rule); border-radius:8px; background:var(--planr-color-raised); }
|
|
38
|
+
.design-handoff-summary strong { font-size:13px; line-height:1.45; }
|
|
39
|
+
.design-handoff-empty { display:grid; gap:7px; margin:12px 0 18px; padding:18px 14px; border:1px dashed var(--planr-color-rule); border-radius:8px; }
|
|
40
|
+
.design-handoff-empty p { color:var(--planr-color-text-muted); font-size:12px; }
|
|
41
|
+
.design-handoff-checks,.design-handoff-requirement-list { display:grid; gap:8px; margin:0 0 18px; padding:0; list-style:none; }
|
|
42
|
+
.design-handoff-checks li,.design-handoff-requirement-list li { display:grid; gap:8px; padding:10px; border:1px solid var(--planr-color-rule); border-radius:7px; }
|
|
43
|
+
.design-handoff-checks li > div { display:flex; align-items:flex-start; justify-content:space-between; gap:8px; }
|
|
44
|
+
.design-handoff-checks strong,.design-handoff-requirement-list strong { font-size:12px; line-height:1.45; }
|
|
45
|
+
.design-handoff-link { min-height:32px!important; padding:4px 0!important; border:0!important; background:transparent!important; color:var(--planr-color-primary)!important; text-align:left; }
|
|
46
|
+
.design-handoff-review-groups { display:grid; gap:10px; margin-bottom:18px; }
|
|
47
|
+
.design-handoff-review-group { display:grid; gap:7px; }
|
|
48
|
+
.design-handoff-review-group article { display:grid; gap:4px; padding:9px 10px; border-left:2px solid var(--planr-color-rule); background:var(--planr-color-panel); font-size:12px; }
|
|
49
|
+
.design-handoff-refinement { color:var(--planr-color-text-muted); }
|
|
50
|
+
.design-handoff-package-editor,.design-handoff-requirements,.design-handoff-package-editor fieldset { display:grid; gap:12px; min-width:0; }
|
|
51
|
+
.design-handoff-package-editor > label,.design-handoff-package-editor fieldset label,.design-handoff-content > label { display:grid; gap:6px; margin-bottom:14px; color:var(--planr-color-text-muted); font-size:12px; }
|
|
52
|
+
.design-handoff-package-editor input,.design-handoff-package-editor textarea,.design-handoff-content > label input,.design-handoff-plan-result textarea { width:100%; min-height:44px; border:1px solid var(--planr-color-rule); border-radius:6px; padding:9px 10px; color:var(--planr-color-text); background:var(--planr-color-panel); }
|
|
53
|
+
.design-handoff-package-editor textarea { min-height:82px; resize:vertical; }
|
|
54
|
+
.design-handoff-package-editor fieldset { margin:0; border:1px solid var(--planr-color-rule); border-radius:7px; padding:10px; }
|
|
55
|
+
.design-handoff-package-editor legend { padding:0 5px; color:var(--planr-color-text); font-size:11px; text-transform:capitalize; }
|
|
56
|
+
.design-handoff-content details { margin:14px 0; border-top:1px solid var(--planr-color-rule); padding-top:12px; }
|
|
57
|
+
.design-handoff-content summary { min-height:36px; cursor:pointer; color:var(--planr-color-text-muted); font-size:12px; }
|
|
58
|
+
.design-handoff-approval-card { display:grid; gap:9px; margin-bottom:18px; padding:16px; border:1px solid var(--planr-color-rule); border-radius:8px; background:var(--planr-color-raised); }
|
|
59
|
+
.design-handoff-plan-result { display:grid; gap:10px; margin-top:14px; }
|
|
60
|
+
.design-handoff-plan-result code { display:block; overflow:auto; border:1px solid var(--planr-color-rule); border-radius:6px; padding:10px; background:var(--planr-color-background); font-size:11px; white-space:pre; }
|
|
61
|
+
.design-handoff-copy { width:100%; }
|
|
62
|
+
.design-handoff-center > footer { display:flex; align-items:center; justify-content:space-between; gap:10px; min-height:50px; padding:8px 12px; border-top:1px solid var(--planr-color-rule); background:var(--planr-color-chrome); }
|
|
63
|
+
.design-handoff-center > footer p { flex:1; color:var(--planr-color-text-muted); font-size:10px; }
|
|
64
|
+
.design-handoff-center > footer p[data-error="true"] { color:var(--planr-color-danger); }
|
|
65
|
+
.design-handoff-center > footer button { flex:none; }
|
|
66
|
+
.planr-shell[data-design-handoff-open="true"] .planr-review-rail { visibility:hidden; pointer-events:none; }
|
|
67
|
+
@media (max-width:960px) {
|
|
68
|
+
.design-handoff-center { position:absolute; width:min(380px,calc(100% - 16px)); }
|
|
69
|
+
}
|
|
70
|
+
@media (max-width:680px) {
|
|
71
|
+
.design-handoff-center { inset:var(--planr-toolbar-height) 0 0; width:100%; max-width:none; border-left:0; }
|
|
72
|
+
.design-handoff-center button,.design-handoff-center input,.design-handoff-center textarea,.design-handoff-center summary { min-height:44px; }
|
|
73
|
+
.design-handoff-center input,.design-handoff-center textarea { font-size:16px; }
|
|
74
|
+
.design-handoff-progress { position:sticky; z-index:2; top:0; background:var(--planr-color-chrome); }
|
|
75
|
+
.design-handoff-content { padding-inline:16px; }
|
|
76
|
+
}
|
|
77
|
+
@media (prefers-reduced-motion:reduce) { .design-handoff-center * { scroll-behavior:auto!important; transition:none!important; animation:none!important; } }
|
|
78
|
+
@media (forced-colors:active) {
|
|
79
|
+
.design-handoff-center,.design-handoff-summary,.design-handoff-empty,.design-handoff-checks li,.design-handoff-requirement-list li { border-color:CanvasText; }
|
|
80
|
+
.design-handoff-badge::before { forced-color-adjust:none; background:currentColor; }
|
|
81
|
+
}
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
/* Five-step Design Handoff Center. Domain decisions stay in server projections. */
|
|
2
|
+
(() => {
|
|
3
|
+
'use strict';
|
|
4
|
+
if (document.currentScript && !document.currentScript.isConnected) return;
|
|
5
|
+
|
|
6
|
+
const q = (selector, parent = document) => parent.querySelector(selector);
|
|
7
|
+
const node = (tag, text = '', attrs = {}) => {
|
|
8
|
+
const value = document.createElement(tag);
|
|
9
|
+
if (text) value.textContent = text;
|
|
10
|
+
for (const [key, entry] of Object.entries(attrs)) if (entry !== undefined && entry !== null && entry !== false) value.setAttribute(key, entry);
|
|
11
|
+
return value;
|
|
12
|
+
};
|
|
13
|
+
const action = (label, run, attrs = {}) => {
|
|
14
|
+
const value = node('button', label, { type: 'button', ...attrs });
|
|
15
|
+
value.addEventListener('click', run);
|
|
16
|
+
return value;
|
|
17
|
+
};
|
|
18
|
+
const plainPackage = value => ({
|
|
19
|
+
id: value.id,
|
|
20
|
+
title: value.title,
|
|
21
|
+
sources: structuredClone(value.sources || []),
|
|
22
|
+
requirements: (value.requirements || []).map(({ id: _id, ...requirement }) => structuredClone(requirement)),
|
|
23
|
+
});
|
|
24
|
+
const requestId = prefix => `${prefix}-${globalThis.crypto?.randomUUID?.() || `${Date.now()}-${Math.random().toString(16).slice(2)}`}`;
|
|
25
|
+
const labels = { ready: 'Ready', pass: 'Complete', attention: 'Needs attention', blocked: 'Blocked', stale: 'Out of date', absent: 'Not available', approved: 'Approved', draft: 'Draft', superseded: 'Superseded', revoked: 'Revoked' };
|
|
26
|
+
const steps = [
|
|
27
|
+
['readiness', 'Readiness'],
|
|
28
|
+
['review', 'Review resolution'],
|
|
29
|
+
['package', 'Implementation package'],
|
|
30
|
+
['approval', 'Owner approval'],
|
|
31
|
+
['plan', 'Continue to Plan'],
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
let bridge, root, panel, opener, activeStep = 'readiness', serial = 0;
|
|
35
|
+
const state = { readiness: null, review: null, implementation: null, errors: {} };
|
|
36
|
+
|
|
37
|
+
const readiness = () => state.readiness?.readiness || state.readiness;
|
|
38
|
+
const approvedReview = () => Boolean(state.review?.current && state.review?.draft?.status === 'approved');
|
|
39
|
+
const packageDraft = () => state.implementation?.draft || null;
|
|
40
|
+
const currentPackage = () => state.implementation?.current || null;
|
|
41
|
+
const approvedPackage = () => currentPackage()?.status === 'approved';
|
|
42
|
+
const statusFor = id => {
|
|
43
|
+
if (state.errors[id === 'readiness' ? 'readiness' : id === 'review' ? 'review' : 'implementation']) return 'attention';
|
|
44
|
+
if (id === 'readiness') return readiness()?.status || 'absent';
|
|
45
|
+
if (id === 'review') return approvedReview() ? 'pass' : state.review?.draft ? (state.review.current ? 'attention' : 'stale') : 'blocked';
|
|
46
|
+
if (id === 'package') return packageDraft() ? 'pass' : readiness()?.status === 'ready' ? 'attention' : 'blocked';
|
|
47
|
+
if (id === 'approval') return approvedPackage() ? 'pass' : currentPackage()?.status || (packageDraft() ? 'attention' : 'blocked');
|
|
48
|
+
return approvedPackage() ? 'ready' : 'blocked';
|
|
49
|
+
};
|
|
50
|
+
const badge = value => node('span', labels[value] || value, { class: 'design-handoff-badge', 'data-status': value });
|
|
51
|
+
const paragraph = (text, className = '') => node('p', text, className ? { class: className } : {});
|
|
52
|
+
const empty = (title, detail) => {
|
|
53
|
+
const value = node('div', '', { class: 'design-handoff-empty' });
|
|
54
|
+
value.append(node('strong', title), paragraph(detail));
|
|
55
|
+
return value;
|
|
56
|
+
};
|
|
57
|
+
const sectionTitle = (eyebrow, title, detail) => {
|
|
58
|
+
const block = node('header', '', { class: 'design-handoff-step-header' });
|
|
59
|
+
block.append(node('span', eyebrow, { class: 'design-handoff-eyebrow' }), node('h3', title), paragraph(detail));
|
|
60
|
+
return block;
|
|
61
|
+
};
|
|
62
|
+
const setStatus = (message, error = false) => {
|
|
63
|
+
const live = q('[data-design-handoff-status]', panel);
|
|
64
|
+
if (!live) return;
|
|
65
|
+
live.textContent = message;
|
|
66
|
+
live.dataset.error = String(error);
|
|
67
|
+
};
|
|
68
|
+
const setBusy = busy => {
|
|
69
|
+
panel?.toggleAttribute('aria-busy', busy);
|
|
70
|
+
panel && q('[data-design-handoff-refresh]', panel)?.toggleAttribute('disabled', busy);
|
|
71
|
+
};
|
|
72
|
+
const perform = async (message, work, success) => {
|
|
73
|
+
setBusy(true); setStatus(message);
|
|
74
|
+
try {
|
|
75
|
+
await work();
|
|
76
|
+
await refresh();
|
|
77
|
+
setStatus(success);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
setStatus(error?.message || 'The handoff action could not be completed. Refresh and try again.', true);
|
|
80
|
+
} finally { setBusy(false); }
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
function renderProgress() {
|
|
84
|
+
const progress = q('[data-design-handoff-progress]', panel);
|
|
85
|
+
progress.replaceChildren();
|
|
86
|
+
for (const [index, [id, label]] of steps.entries()) {
|
|
87
|
+
const status = statusFor(id);
|
|
88
|
+
const button = action(label, () => { activeStep = id; render(); q(`[data-handoff-step="${id}"]`, panel)?.focus(); }, {
|
|
89
|
+
class: 'design-handoff-step',
|
|
90
|
+
'data-handoff-step': id,
|
|
91
|
+
'data-status': status,
|
|
92
|
+
'aria-current': activeStep === id ? 'step' : undefined,
|
|
93
|
+
'aria-label': `${index + 1}. ${label}: ${labels[status] || status}`,
|
|
94
|
+
});
|
|
95
|
+
button.prepend(node('span', String(index + 1).padStart(2, '0'), { 'aria-hidden': 'true' }));
|
|
96
|
+
button.append(badge(status));
|
|
97
|
+
progress.append(button);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function recoveryAction(check) {
|
|
102
|
+
const label = check.recoveryAction?.label || 'Return to the design';
|
|
103
|
+
return action(label, () => {
|
|
104
|
+
if (['resolve-review-decisions', 'resolve-review-blockers', 'approve-review-handoff'].includes(check.recoveryAction?.id)) {
|
|
105
|
+
activeStep = 'review'; render();
|
|
106
|
+
} else {
|
|
107
|
+
close(); bridge.announce(`${check.message} ${label}.`);
|
|
108
|
+
}
|
|
109
|
+
}, { class: 'design-handoff-link' });
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function renderReadiness(content) {
|
|
113
|
+
const value = readiness();
|
|
114
|
+
content.append(sectionTitle('Step 1 of 5', 'Readiness', 'Confirm the current design, verification and review evidence before preparing implementation work.'));
|
|
115
|
+
if (state.errors.readiness) {
|
|
116
|
+
content.append(empty('Readiness is temporarily unavailable', state.errors.readiness));
|
|
117
|
+
content.append(action('Retry readiness', refresh, { class: 'design-primary' }));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
if (!value || value.status === 'absent') {
|
|
121
|
+
content.append(empty('No readiness projection yet', value?.message || 'Complete a local design render to compute handoff readiness.'));
|
|
122
|
+
content.append(action(value?.nextAction?.label || 'Return to the design', close, { class: 'design-primary' }));
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const summary = node('div', '', { class: 'design-handoff-summary' });
|
|
126
|
+
summary.append(badge(value.status), node('strong', value.status === 'ready' ? 'This design can move into implementation packaging.' : `${value.blockers?.length || 0} blocking checks remain.`));
|
|
127
|
+
content.append(summary);
|
|
128
|
+
const list = node('ol', '', { class: 'design-handoff-checks' });
|
|
129
|
+
for (const check of value.checks || []) {
|
|
130
|
+
const item = node('li', '', { 'data-status': check.status });
|
|
131
|
+
const body = node('div'); body.append(node('strong', check.message), badge(check.status));
|
|
132
|
+
item.append(body);
|
|
133
|
+
if (check.status !== 'pass') item.append(recoveryAction(check));
|
|
134
|
+
list.append(item);
|
|
135
|
+
}
|
|
136
|
+
content.append(list);
|
|
137
|
+
if (value.status === 'ready') content.append(action('Review resolved feedback', () => { activeStep = 'review'; render(); }, { class: 'design-primary' }));
|
|
138
|
+
else if (value.nextActions?.[0]) content.append(recoveryAction({ message: 'Readiness needs attention.', recoveryAction: value.nextActions[0] }));
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function reviewGroup(title, items) {
|
|
142
|
+
const section = node('section', '', { class: 'design-handoff-review-group' });
|
|
143
|
+
section.append(node('h4', `${title} · ${items?.length || 0}`));
|
|
144
|
+
if (!items?.length) section.append(paragraph('None recorded.', 'design-muted'));
|
|
145
|
+
for (const item of items || []) {
|
|
146
|
+
const card = node('article');
|
|
147
|
+
card.append(paragraph(item.text || item.statement || 'Review decision'));
|
|
148
|
+
if (item.refinement) card.append(paragraph(item.refinement, 'design-handoff-refinement'));
|
|
149
|
+
section.append(card);
|
|
150
|
+
}
|
|
151
|
+
return section;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function renderReview(content) {
|
|
155
|
+
content.append(sectionTitle('Step 2 of 5', 'Review resolution', 'Turn current feedback into explicit accepted, open, deferred and declined decisions.'));
|
|
156
|
+
if (state.errors.review) {
|
|
157
|
+
content.append(empty('Review decisions could not be loaded', state.errors.review));
|
|
158
|
+
content.append(action('Retry review decisions', refresh, { class: 'design-primary' }));
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
const response = state.review, draft = response?.draft;
|
|
162
|
+
if (!draft) {
|
|
163
|
+
content.append(empty('No review handoff has been prepared', 'Generate a proposal from the current comments. You will review every decision before approval.'));
|
|
164
|
+
content.append(action('Generate review handoff', () => perform('Generating review decisions…', async () => {
|
|
165
|
+
state.review = await bridge.updateReviewHandoff({ action: 'draft', version: 0 });
|
|
166
|
+
}, 'Review decisions generated.'), { class: 'design-primary' }));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const status = response.current ? draft.status : 'stale';
|
|
170
|
+
const summary = node('div', '', { class: 'design-handoff-summary' });
|
|
171
|
+
summary.append(badge(status), node('strong', draft.content?.summary || (response.current ? 'Review decisions match this design.' : 'The design or feedback changed after this handoff was prepared.')));
|
|
172
|
+
content.append(summary);
|
|
173
|
+
const decisions = node('div', '', { class: 'design-handoff-review-groups' });
|
|
174
|
+
decisions.append(
|
|
175
|
+
reviewGroup('Accepted changes', draft.content?.agreedChanges),
|
|
176
|
+
reviewGroup('Open questions', draft.content?.openQuestions),
|
|
177
|
+
reviewGroup('Deferred', draft.content?.deferred),
|
|
178
|
+
reviewGroup('Declined', draft.content?.rejected),
|
|
179
|
+
);
|
|
180
|
+
content.append(decisions);
|
|
181
|
+
const label = approvedReview() ? 'Continue to implementation package' : response.current ? 'Review and approve decisions' : 'Regenerate current decisions';
|
|
182
|
+
content.append(action(label, () => {
|
|
183
|
+
if (approvedReview()) { activeStep = 'package'; render(); }
|
|
184
|
+
else bridge.openReviewHandoff();
|
|
185
|
+
}, { class: 'design-primary' }));
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function packageEditor(source, { regenerate = false } = {}) {
|
|
189
|
+
const form = node('form', '', { class: 'design-handoff-package-editor' });
|
|
190
|
+
const editable = plainPackage(source);
|
|
191
|
+
const title = node('input', '', { type: 'text', required: '', maxlength: '256', 'aria-label': 'Implementation package title' });
|
|
192
|
+
title.value = editable.title;
|
|
193
|
+
const titleLabel = node('label', 'Package title'); titleLabel.append(title); form.append(titleLabel);
|
|
194
|
+
const requirements = node('div', '', { class: 'design-handoff-requirements' });
|
|
195
|
+
const editors = editable.requirements.map((requirement, index) => {
|
|
196
|
+
const card = node('fieldset'); card.append(node('legend', `${requirement.kind.replaceAll('-', ' ')} requirement ${index + 1}`));
|
|
197
|
+
const statement = node('textarea', '', { required: '', maxlength: '16384', 'aria-label': `Requirement ${index + 1} statement` }); statement.value = requirement.statement;
|
|
198
|
+
const verification = node('textarea', '', { required: '', maxlength: '16384', 'aria-label': `Requirement ${index + 1} verification` }); verification.value = requirement.verification.join('\n');
|
|
199
|
+
const statementLabel = node('label', 'Requirement'); statementLabel.append(statement);
|
|
200
|
+
const verificationLabel = node('label', 'Verification, one expectation per line'); verificationLabel.append(verification);
|
|
201
|
+
card.append(statementLabel, verificationLabel); requirements.append(card);
|
|
202
|
+
return { requirement, statement, verification };
|
|
203
|
+
});
|
|
204
|
+
form.append(requirements);
|
|
205
|
+
const save = node('button', regenerate ? 'Create next package version' : 'Save implementation package', { type: 'submit', class: 'design-primary' });
|
|
206
|
+
form.append(save);
|
|
207
|
+
form.addEventListener('submit', event => {
|
|
208
|
+
event.preventDefault();
|
|
209
|
+
if (!form.reportValidity()) return;
|
|
210
|
+
editable.title = title.value.trim();
|
|
211
|
+
editable.requirements = editors.map(({ requirement, statement, verification }) => ({
|
|
212
|
+
...requirement,
|
|
213
|
+
statement: statement.value.trim(),
|
|
214
|
+
verification: verification.value.split('\n').map(value => value.trim()).filter(Boolean),
|
|
215
|
+
}));
|
|
216
|
+
const input = regenerate
|
|
217
|
+
? { action: 'regenerate', requestId: requestId('regenerate'), package: editable }
|
|
218
|
+
: { action: 'draft', package: editable };
|
|
219
|
+
void perform(regenerate ? 'Creating a new package version…' : 'Saving the implementation package…', async () => {
|
|
220
|
+
await bridge.updateImplementationHandoff(input);
|
|
221
|
+
}, regenerate ? 'A new package version is ready for approval.' : 'Implementation package saved.');
|
|
222
|
+
});
|
|
223
|
+
return form;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function renderPackage(content) {
|
|
227
|
+
content.append(sectionTitle('Step 3 of 5', 'Implementation package', 'Review traceable requirements and observable verification expectations. Integrity values stay in the contract and are hidden here.'));
|
|
228
|
+
if (state.errors.implementation) {
|
|
229
|
+
content.append(empty('Implementation package could not be loaded', state.errors.implementation));
|
|
230
|
+
content.append(action('Retry implementation package', refresh, { class: 'design-primary' }));
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const response = state.implementation, draft = response?.draft;
|
|
234
|
+
if (!approvedReview() || readiness()?.status !== 'ready') {
|
|
235
|
+
content.append(empty('Readiness must be complete first', 'Approve current review decisions and resolve every blocking readiness check before composing the implementation package.'));
|
|
236
|
+
content.append(action('Return to readiness', () => { activeStep = 'readiness'; render(); }, { class: 'design-primary' }));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (!draft) {
|
|
240
|
+
const proposal = response?.proposal;
|
|
241
|
+
if (!proposal) {
|
|
242
|
+
content.append(empty('No package proposal is available', 'Refresh the current design render, then return here to prepare the implementation scope.'));
|
|
243
|
+
content.append(action('Refresh package proposal', refresh, { class: 'design-primary' }));
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
content.append(paragraph(`${proposal.sources.length} approved sources · ${proposal.requirements.length} proposed requirements`, 'design-muted'));
|
|
247
|
+
content.append(packageEditor(proposal));
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const summary = node('div', '', { class: 'design-handoff-summary' });
|
|
251
|
+
summary.append(badge(draft.status), node('strong', `${draft.title} · version ${draft.version}`));
|
|
252
|
+
content.append(summary, paragraph(`${draft.sources.length} sources · ${draft.requirements.length} requirements`, 'design-muted'));
|
|
253
|
+
const list = node('ol', '', { class: 'design-handoff-requirement-list' });
|
|
254
|
+
for (const requirement of draft.requirements) {
|
|
255
|
+
const item = node('li');
|
|
256
|
+
item.append(node('strong', requirement.statement), paragraph(requirement.verification.join(' · '), 'design-muted'));
|
|
257
|
+
list.append(item);
|
|
258
|
+
}
|
|
259
|
+
content.append(list);
|
|
260
|
+
if (approvedPackage()) {
|
|
261
|
+
const edit = node('details'); edit.append(node('summary', 'Prepare a revised package version'), packageEditor(draft, { regenerate: true })); content.append(edit);
|
|
262
|
+
}
|
|
263
|
+
content.append(action('Review owner approval', () => { activeStep = 'approval'; render(); }, { class: 'design-primary' }));
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
function renderApproval(content) {
|
|
267
|
+
content.append(sectionTitle('Step 4 of 5', 'Owner approval', 'Approval records the exact package version and permits a later, separate Plan invocation.'));
|
|
268
|
+
if (state.errors.implementation) {
|
|
269
|
+
content.append(empty('Approval state could not be loaded', state.errors.implementation));
|
|
270
|
+
content.append(action('Retry approval state', refresh, { class: 'design-primary' }));
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
const response = state.implementation, draft = response?.draft, current = response?.current, preview = response?.approvalPreview;
|
|
274
|
+
if (!draft) {
|
|
275
|
+
content.append(empty('No package is ready for approval', 'Complete the implementation package before requesting owner approval.'));
|
|
276
|
+
content.append(action('Prepare implementation package', () => { activeStep = 'package'; render(); }, { class: 'design-primary' }));
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
if (approvedPackage()) {
|
|
280
|
+
const approved = response.history?.find(item => item.id === current.id && item.version === current.version);
|
|
281
|
+
content.append(node('div', '', { class: 'design-handoff-approval-card' }));
|
|
282
|
+
const card = content.lastElementChild;
|
|
283
|
+
card.append(badge('approved'), node('h4', approved?.title || draft.title), paragraph(`Version ${current.version} was approved${approved?.approval?.actorId ? ` by ${approved.approval.actorId}` : ''}${approved?.approval?.approvedAt ? ` on ${new Date(approved.approval.approvedAt).toLocaleString()}` : ''}.`));
|
|
284
|
+
content.append(action('Continue to Plan', () => { activeStep = 'plan'; render(); }, { class: 'design-primary' }));
|
|
285
|
+
const details = node('details'); details.append(node('summary', 'Revoke this approval'));
|
|
286
|
+
const reason = node('textarea', '', { 'aria-label': 'Revocation reason', maxlength: '16384', placeholder: 'Why is this package no longer approved?' });
|
|
287
|
+
details.append(reason, action('Revoke approval', () => {
|
|
288
|
+
if (!reason.value.trim()) { reason.setCustomValidity('Enter a revocation reason.'); reason.reportValidity(); return; }
|
|
289
|
+
reason.setCustomValidity('');
|
|
290
|
+
void perform('Revoking approval…', async () => bridge.updateImplementationHandoff({ action: 'revoke', requestId: requestId('revoke'), expectedVersion: current.version, expectedContentDigest: current.contentDigest, reason: reason.value.trim() }), 'Approval revoked.');
|
|
291
|
+
}, { class: 'design-danger' }));
|
|
292
|
+
content.append(details);
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
if (!preview?.available) {
|
|
296
|
+
content.append(empty(current?.status === 'revoked' ? 'The current approval was revoked' : 'This package cannot be approved yet', 'Refresh the package against the current ready design, then review approval again.'));
|
|
297
|
+
content.append(action('Review implementation package', () => { activeStep = 'package'; render(); }, { class: 'design-primary' }));
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
const card = node('div', '', { class: 'design-handoff-approval-card' });
|
|
301
|
+
card.append(node('h4', preview.summary.title), paragraph(`Version ${preview.summary.packageVersion} · ${preview.summary.requirementCount} requirements · direction ${preview.summary.selectedVariant}`), paragraph(preview.summary.description, 'design-muted'));
|
|
302
|
+
content.append(card, action('Approve exact package', () => perform('Recording owner approval…', async () => {
|
|
303
|
+
await bridge.updateImplementationHandoff({ action: 'approve', requestId: requestId('approve'), ...preview.approvalRequest });
|
|
304
|
+
}, 'Implementation package approved.'), { class: 'design-primary' }));
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async function copyText(value, output) {
|
|
308
|
+
try { await navigator.clipboard.writeText(value); output.textContent = 'Invocation copied. Paste it into your active coding agent when you are ready.'; }
|
|
309
|
+
catch {
|
|
310
|
+
const field = node('textarea', '', { readonly: '', 'aria-label': 'Plan invocation' }); field.value = value; output.before(field); field.focus(); field.select(); output.textContent = 'Copy the selected invocation.';
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function renderPlan(content) {
|
|
315
|
+
content.append(sectionTitle('Step 5 of 5', 'Continue to Plan', 'Prepare an explicit host handoff. Plan remains a separate action; Ship is never started here.'));
|
|
316
|
+
if (!approvedPackage()) {
|
|
317
|
+
content.append(empty('An approved current package is required', currentPackage()?.status === 'revoked' ? 'This package was revoked. Prepare and approve a current version before planning.' : 'Complete owner approval before preparing the Plan invocation.'));
|
|
318
|
+
content.append(action('Review owner approval', () => { activeStep = 'approval'; render(); }, { class: 'design-primary' }));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const context = bridge.context();
|
|
322
|
+
const subject = node('input', '', { type: 'text', required: '', maxlength: '256', value: context.designId, 'aria-label': 'Plan subject' });
|
|
323
|
+
const label = node('label', 'Plan subject'); label.append(subject); content.append(label);
|
|
324
|
+
const result = node('div', '', { class: 'design-handoff-plan-result', 'aria-live': 'polite' });
|
|
325
|
+
content.append(action('Prepare Plan handoff', async () => {
|
|
326
|
+
setBusy(true); setStatus('Preparing the Plan handoff…');
|
|
327
|
+
try {
|
|
328
|
+
const response = await bridge.updateImplementationHandoff({ action: 'continue-to-plan', subject: subject.value.trim() });
|
|
329
|
+
const handoff = response.handoff;
|
|
330
|
+
if (typeof bridge.continueToActiveHost === 'function') {
|
|
331
|
+
await bridge.continueToActiveHost(handoff);
|
|
332
|
+
result.textContent = 'The approved package was handed to the active host. Invoke Plan there to continue.';
|
|
333
|
+
} else {
|
|
334
|
+
const host = String(context.host || '').toLowerCase();
|
|
335
|
+
const key = host.includes('claude') ? 'claudeCode' : host.includes('codex') ? 'codex' : host.includes('cursor') ? 'cursor' : 'fallback';
|
|
336
|
+
const invocation = handoff.invocations[key] || handoff.invocations.fallback;
|
|
337
|
+
result.replaceChildren(node('code', invocation));
|
|
338
|
+
result.append(action('Copy invocation', () => void copyText(invocation, result), { class: 'design-handoff-copy' }));
|
|
339
|
+
}
|
|
340
|
+
setStatus('Plan handoff prepared. No planning files, agents, Git state or Ship run were changed.');
|
|
341
|
+
} catch (error) { setStatus(error?.message || 'The Plan handoff could not be prepared.', true); }
|
|
342
|
+
finally { setBusy(false); }
|
|
343
|
+
}, { class: 'design-primary' }), result);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function render() {
|
|
347
|
+
if (!panel) return;
|
|
348
|
+
renderProgress();
|
|
349
|
+
const content = q('[data-design-handoff-content]', panel); content.replaceChildren();
|
|
350
|
+
({ readiness: renderReadiness, review: renderReview, package: renderPackage, approval: renderApproval, plan: renderPlan })[activeStep](content);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
async function refresh() {
|
|
354
|
+
const attempt = ++serial;
|
|
355
|
+
setBusy(true); setStatus('Refreshing handoff state…');
|
|
356
|
+
const results = await Promise.allSettled([bridge.loadReadiness(), bridge.loadReviewHandoff(), bridge.loadImplementationHandoff()]);
|
|
357
|
+
if (attempt !== serial || !panel) return;
|
|
358
|
+
for (const [index, key] of ['readiness', 'review', 'implementation'].entries()) {
|
|
359
|
+
const result = results[index];
|
|
360
|
+
if (result.status === 'fulfilled') { state[key] = result.value; delete state.errors[key]; }
|
|
361
|
+
else state.errors[key] = result.reason?.message || 'This state is temporarily unavailable.';
|
|
362
|
+
}
|
|
363
|
+
setBusy(false); setStatus(Object.keys(state.errors).length ? 'Some handoff state is unavailable. The current design remains usable.' : 'Handoff state is current.', Boolean(Object.keys(state.errors).length));
|
|
364
|
+
render();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function close() {
|
|
368
|
+
if (!panel) return;
|
|
369
|
+
const returnTo = opener; panel.remove(); panel = null; delete root.dataset.designHandoffOpen;
|
|
370
|
+
returnTo?.isConnected && returnTo.focus({ preventScroll: true });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function open(step = activeStep) {
|
|
374
|
+
if (!bridge.owner()) return bridge.announce('Only a design owner can prepare an implementation handoff.', true);
|
|
375
|
+
activeStep = steps.some(([id]) => id === step) ? step : 'readiness';
|
|
376
|
+
if (panel) { render(); q(`[data-handoff-step="${activeStep}"]`, panel)?.focus(); return; }
|
|
377
|
+
opener = document.activeElement; root.dataset.designHandoffOpen = 'true';
|
|
378
|
+
panel = node('aside', '', { class: 'design-handoff-center', role: 'dialog', 'aria-modal': 'false', 'aria-labelledby': 'design-handoff-center-title' });
|
|
379
|
+
const header = node('header');
|
|
380
|
+
const heading = node('div'); heading.append(node('span', 'Owner workspace', { class: 'design-handoff-eyebrow' }), node('h2', 'Handoff Center', { id: 'design-handoff-center-title' }));
|
|
381
|
+
header.append(heading, action('×', close, { 'aria-label': 'Close Handoff Center', class: 'design-handoff-close' }));
|
|
382
|
+
const progress = node('nav', '', { class: 'design-handoff-progress', 'data-design-handoff-progress': '', 'aria-label': 'Design handoff progress' });
|
|
383
|
+
const content = node('section', '', { class: 'design-handoff-content', 'data-design-handoff-content': '', tabindex: '-1' });
|
|
384
|
+
const footer = node('footer'); footer.append(node('p', '', { 'data-design-handoff-status': '', role: 'status', 'aria-live': 'polite' }), action('Refresh', refresh, { 'data-design-handoff-refresh': '' }));
|
|
385
|
+
panel.append(header, progress, content, footer); root.append(panel);
|
|
386
|
+
panel.addEventListener('keydown', event => { if (event.key === 'Escape') { event.preventDefault(); close(); } });
|
|
387
|
+
render(); q(`[data-handoff-step="${activeStep}"]`, panel)?.focus({ preventScroll: true }); void refresh();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function mount() {
|
|
391
|
+
bridge = window.__openPlanrDesignHandoffBridge;
|
|
392
|
+
root = q('.planr-shell');
|
|
393
|
+
if (!bridge || !root) return false;
|
|
394
|
+
window.__openPlanrDesignHandoffCenter = Object.freeze({ open, close, refresh, getState: () => ({ open: Boolean(panel), step: activeStep, statuses: Object.fromEntries(steps.map(([id]) => [id, statusFor(id)])) }) });
|
|
395
|
+
return true;
|
|
396
|
+
}
|
|
397
|
+
const started = Date.now();
|
|
398
|
+
function boot() { if (!mount() && Date.now() - started < 15000) setTimeout(boot, 30); }
|
|
399
|
+
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true }); else boot();
|
|
400
|
+
})();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openplanr/protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Canonical OpenPlanr schemas, registries, typed errors, and portable contract validation",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"types": "./src/design-publication-contracts.d.mts",
|
|
48
48
|
"import": "./src/design-publication-contracts.mjs"
|
|
49
49
|
},
|
|
50
|
+
"./design-handoff-contracts": {
|
|
51
|
+
"types": "./src/design-handoff-contracts.d.mts",
|
|
52
|
+
"import": "./src/design-handoff-contracts.mjs"
|
|
53
|
+
},
|
|
50
54
|
"./registries": {
|
|
51
55
|
"types": "./src/registries.d.mts",
|
|
52
56
|
"import": "./src/registries.mjs"
|