Skip to content

Commit 0499e2d

Browse files
author
DavidQ
committed
Centralize Workspace V2 session state into single computed model and remove scattered UI logic - PR_11_264
1 parent 1eb8bfc commit 0499e2d

5 files changed

Lines changed: 370 additions & 73 deletions
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# PR_11_264 Session State Model Consolidation Report
2+
3+
## Scope
4+
Workspace V2 only (session library / diff / merge state model wiring).
5+
6+
## Files Changed
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2SessionStateModelConsolidation.test.mjs
9+
- docs/pr/PLAN_PR_11_264_WORKSPACE_V2_SESSION_STATE_MODEL_CONSOLIDATION.md
10+
- docs/pr/BUILD_PR_11_264_WORKSPACE_V2_SESSION_STATE_MODEL_CONSOLIDATION.md
11+
12+
## Implementation Summary
13+
- Added unified computed model method:
14+
- `computeWorkspaceSessionUiStateModel()`
15+
- Added unified render method:
16+
- `renderWorkspaceSessionUiStateModel(model)`
17+
- Added single refresh entrypoint:
18+
- `refreshWorkspaceSessionUiStateModel()`
19+
- Routed session UI decision methods to refresh entrypoint:
20+
- `updateSessionLibraryActionState()`
21+
- `updateDiffSelectionFeedbackAndState()`
22+
- `updateMergeSelectionFeedbackAndState()`
23+
- `updateUndoLastMergeState()`
24+
- Model now drives in one place:
25+
- library action button enable/disable
26+
- diff/merge action button enable/disable
27+
- diff/merge selection + enable text
28+
- undo availability
29+
- merge preview visibility
30+
- Existing authoritative merge record logic remains the undo truth source.
31+
- `clearMergePanelTransientState(...)` now updates transient data, then re-renders from model.
32+
33+
## PR_11_263 Regression Check
34+
- Preserved stabilized enable-state/status wording from PR_11_263:
35+
- `Compute Diff is enabled.`
36+
- `Preview Merge is enabled.`
37+
- `Confirm Preview is enabled.`
38+
- `Apply Merge is enabled.`
39+
- `Preview confirmed. Apply Merge is enabled.`
40+
41+
## Validation Commands
42+
1. `node --check tools/workspace-v2/index.js`
43+
- PASS
44+
2. `node --check tests/runtime/V2SessionStateModelConsolidation.test.mjs`
45+
- PASS
46+
3. `node tests/runtime/V2SessionStateModelConsolidation.test.mjs`
47+
- PASS
48+
- Results file: `tmp/v2-session-state-model-consolidation-results.json`
49+
- Failures: `[]`
50+
51+
## Full Samples Smoke Decision
52+
- Skipped full samples smoke test.
53+
- Reason: PR scope is Workspace V2 session UI state-model consolidation with targeted runtime coverage only.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# BUILD_PR_11_264_WORKSPACE_V2_SESSION_STATE_MODEL_CONSOLIDATION
2+
3+
## Purpose
4+
Implement a computed single-source session UI state model for Workspace V2 to reduce churn and stale decision paths.
5+
6+
## Files
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2SessionStateModelConsolidation.test.mjs
9+
- docs/dev/reports/PR_11_264_session_state_model_consolidation_report.md
10+
11+
## Implementation
12+
1. Add unified state model computation method.
13+
2. Add unified UI render method that applies the model.
14+
3. Add one refresh entrypoint used by session UI handlers.
15+
4. Route library/diff/merge/undo handler update methods to refresh entrypoint.
16+
5. Keep authoritative merge-record validation as the undo truth source.
17+
6. Preserve PR_11_263 UX behavior while removing duplicated condition trees.
18+
19+
## Acceptance
20+
- UI state is derived from the computed model only.
21+
- Undo availability derives from authoritative merge record only.
22+
- No stale merge preview/status paths from scattered checks.
23+
- No regressions in PR_11_263 stabilized messages and enablement semantics.
24+
25+
## Validation
26+
- node --check tools/workspace-v2/index.js
27+
- node --check tests/runtime/V2SessionStateModelConsolidation.test.mjs
28+
- node tests/runtime/V2SessionStateModelConsolidation.test.mjs
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# PLAN_PR_11_264_WORKSPACE_V2_SESSION_STATE_MODEL_CONSOLIDATION
2+
3+
## Purpose
4+
Consolidate Workspace V2 session/merge UI decision logic into one computed internal state model.
5+
6+
## Scope
7+
- tools/workspace-v2/index.js
8+
- tests/runtime/V2SessionStateModelConsolidation.test.mjs
9+
- PR docs/report only
10+
11+
## Goals
12+
- Single computed model drives:
13+
- button enable/disable
14+
- status/enable text
15+
- undo availability
16+
- preview visibility
17+
- Remove scattered duplicated condition logic across handlers.
18+
- Recompute model on selection, merge apply, undo, delete, and refresh/load paths.
19+
20+
## Out of Scope
21+
- No schema changes
22+
- No other tools
23+
- No UI redesign
24+
25+
## Validation
26+
- node --check tools/workspace-v2/index.js
27+
- node --check tests/runtime/V2SessionStateModelConsolidation.test.mjs
28+
- node tests/runtime/V2SessionStateModelConsolidation.test.mjs
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
import assert from "node:assert/strict";
2+
import fs from "node:fs";
3+
import path from "node:path";
4+
import { execFileSync } from "node:child_process";
5+
import { fileURLToPath, pathToFileURL } from "node:url";
6+
7+
const __filename = fileURLToPath(import.meta.url);
8+
const __dirname = path.dirname(__filename);
9+
const repoRoot = path.resolve(__dirname, "..", "..");
10+
const jsPath = path.join(repoRoot, "tools", "workspace-v2", "index.js");
11+
const resultsPath = path.join(repoRoot, "tmp", "v2-session-state-model-consolidation-results.json");
12+
13+
function checkSyntax(filePath) {
14+
try {
15+
execFileSync(process.execPath, ["--check", filePath], {
16+
cwd: repoRoot,
17+
stdio: ["ignore", "pipe", "pipe"]
18+
});
19+
return { ok: true, error: "" };
20+
} catch (error) {
21+
return { ok: false, error: (error?.stderr || error?.stdout || error?.message || "").toString().trim() };
22+
}
23+
}
24+
25+
function computeModelState({
26+
hasSessionInput,
27+
diffDistinct,
28+
mergeDistinct,
29+
previewExists,
30+
previewFresh,
31+
previewConfirmed,
32+
previewHasConflicts,
33+
authoritativeUndo
34+
}) {
35+
let mergeEnableText = "Preview Merge is enabled.";
36+
if (!mergeDistinct) {
37+
mergeEnableText = "Select two different sessions to enable Preview Merge.";
38+
} else if (previewExists && !previewFresh) {
39+
mergeEnableText = "Preview is stale. Run Preview Merge again.";
40+
} else if (previewExists && previewHasConflicts) {
41+
mergeEnableText = "Preview has conflicts. Resolve conflicts before applying.";
42+
} else if (previewExists && previewConfirmed && previewFresh && !previewHasConflicts) {
43+
mergeEnableText = "Apply Merge is enabled.";
44+
} else if (previewExists && !previewConfirmed && previewFresh && !previewHasConflicts) {
45+
mergeEnableText = "Confirm Preview is enabled.";
46+
}
47+
return {
48+
libraryButtonsDisabled: !hasSessionInput,
49+
diffCanCompute: diffDistinct,
50+
diffEnableText: diffDistinct ? "Compute Diff is enabled." : "Select two different sessions to enable Compute Diff.",
51+
mergeCanPreview: mergeDistinct,
52+
mergeCanConfirm: previewExists && !previewConfirmed && previewFresh && !previewHasConflicts,
53+
mergeCanApply: previewExists && previewConfirmed && previewFresh && !previewHasConflicts,
54+
mergeEnableText,
55+
undoEnabled: authoritativeUndo
56+
};
57+
}
58+
59+
export function run() {
60+
const failures = [];
61+
const jsExists = fs.existsSync(jsPath);
62+
const js = jsExists ? fs.readFileSync(jsPath, "utf8") : "";
63+
const jsSyntax = checkSyntax(jsPath);
64+
const testSyntax = checkSyntax(path.join(repoRoot, "tests", "runtime", "V2SessionStateModelConsolidation.test.mjs"));
65+
66+
if (!jsExists) failures.push("Missing tools/workspace-v2/index.js.");
67+
if (!jsSyntax.ok) failures.push("tools/workspace-v2/index.js failed syntax check.");
68+
if (!testSyntax.ok) failures.push("tests/runtime/V2SessionStateModelConsolidation.test.mjs failed syntax check.");
69+
70+
const requiredTokens = [
71+
"computeWorkspaceSessionUiStateModel()",
72+
"renderWorkspaceSessionUiStateModel(model)",
73+
"refreshWorkspaceSessionUiStateModel()",
74+
"updateSessionLibraryActionState() {",
75+
"updateDiffSelectionFeedbackAndState() {",
76+
"updateMergeSelectionFeedbackAndState() {",
77+
"updateUndoLastMergeState() {",
78+
"this.refreshWorkspaceSessionUiStateModel();",
79+
"this.mergeOutputNode.hidden = !model.mergePreviewVisible;"
80+
];
81+
requiredTokens.forEach((token) => {
82+
if (!js.includes(token)) failures.push(`Missing consolidated-state-model token/text: ${token}`);
83+
});
84+
85+
const preservedUxTokens = [
86+
"Compute Diff is enabled.",
87+
"Preview Merge is enabled.",
88+
"Confirm Preview is enabled.",
89+
"Apply Merge is enabled.",
90+
"Preview confirmed. Apply Merge is enabled."
91+
];
92+
preservedUxTokens.forEach((token) => {
93+
if (!js.includes(token)) failures.push(`PR_11_263 UX text regression: missing '${token}'`);
94+
});
95+
96+
const stateNoInputNoSelection = computeModelState({
97+
hasSessionInput: false,
98+
diffDistinct: false,
99+
mergeDistinct: false,
100+
previewExists: false,
101+
previewFresh: false,
102+
previewConfirmed: false,
103+
previewHasConflicts: false,
104+
authoritativeUndo: false
105+
});
106+
if (!stateNoInputNoSelection.libraryButtonsDisabled) failures.push("Library buttons should be disabled with empty input.");
107+
if (stateNoInputNoSelection.mergeCanPreview) failures.push("Merge preview should be disabled when selections are not distinct.");
108+
if (stateNoInputNoSelection.undoEnabled) failures.push("Undo should be disabled without authoritative merge record.");
109+
110+
const statePreviewReady = computeModelState({
111+
hasSessionInput: true,
112+
diffDistinct: true,
113+
mergeDistinct: true,
114+
previewExists: true,
115+
previewFresh: true,
116+
previewConfirmed: false,
117+
previewHasConflicts: false,
118+
authoritativeUndo: false
119+
});
120+
if (!statePreviewReady.mergeCanConfirm || statePreviewReady.mergeCanApply) failures.push("Fresh conflict-free preview should enable Confirm only.");
121+
if (statePreviewReady.mergeEnableText !== "Confirm Preview is enabled.") failures.push("Merge enable text should reflect Confirm-enabled state.");
122+
123+
const stateApplyReady = computeModelState({
124+
hasSessionInput: true,
125+
diffDistinct: true,
126+
mergeDistinct: true,
127+
previewExists: true,
128+
previewFresh: true,
129+
previewConfirmed: true,
130+
previewHasConflicts: false,
131+
authoritativeUndo: true
132+
});
133+
if (!stateApplyReady.mergeCanApply) failures.push("Confirmed fresh preview should enable Apply.");
134+
if (stateApplyReady.mergeEnableText !== "Apply Merge is enabled.") failures.push("Merge enable text should reflect Apply-enabled state.");
135+
if (!stateApplyReady.undoEnabled) failures.push("Undo should reflect authoritative availability.");
136+
137+
fs.mkdirSync(path.dirname(resultsPath), { recursive: true });
138+
fs.writeFileSync(resultsPath, `${JSON.stringify({
139+
generatedAt: new Date().toISOString(),
140+
failures,
141+
checks: { jsExists, jsSyntax, testSyntax },
142+
scenarios: { stateNoInputNoSelection, statePreviewReady, stateApplyReady }
143+
}, null, 2)}\n`, "utf8");
144+
145+
console.log(`v2 session-state-model-consolidation results: ${resultsPath}`);
146+
assert.equal(failures.length, 0, `V2 session-state-model-consolidation failures: ${failures.join(" | ")}`);
147+
return { failures };
148+
}
149+
150+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
151+
try {
152+
const summary = run();
153+
console.log(JSON.stringify(summary, null, 2));
154+
} catch (error) {
155+
console.error(error);
156+
process.exitCode = 1;
157+
}
158+
}

0 commit comments

Comments
 (0)