From f79af9b19d25b66bde0485db5bfbe72511381d5b Mon Sep 17 00:00:00 2001 From: DavidQ Date: Sat, 20 Jun 2026 02:11:08 -0400 Subject: [PATCH] PR_26171_031 idea board inline tree grid actions --- docs_build/dev/codex_commands.md | 58 +- docs_build/dev/commit_comment.txt | 8 +- .../dev/reports/codex_changed_files.txt | 94 +- docs_build/dev/reports/codex_review.diff | 3589 ++++++----------- .../reports/coverage_changed_js_guardrail.txt | 4 +- .../dev/reports/dependency_gating_report.md | 2 +- .../dependency_hydration_reuse_report.md | 2 +- .../reports/execution_graph_reuse_report.md | 4 +- .../dev/reports/failure_fingerprint_report.md | 2 +- .../filesystem_scan_reduction_report.md | 2 +- .../reports/incremental_validation_report.md | 4 +- .../dev/reports/lane_compilation_report.md | 2 +- .../dev/reports/lane_deduplication_report.md | 2 +- .../reports/lane_input_validation_report.md | 2 +- .../lane_manifests/workspace-contract.json | 8 +- .../lane_runtime_optimization_report.md | 2 +- .../dev/reports/lane_snapshot_report.md | 4 +- .../lane_snapshots/workspace-contract.json | 16 +- .../dev/reports/lane_warm_start_report.md | 4 +- .../lane_warm_starts/workspace-contract.json | 8 +- .../monolith_trigger_removal_report.md | 2 +- .../persistent_lane_manifest_report.md | 6 +- .../playwright_discovery_ownership_report.md | 2 +- .../playwright_discovery_scope_report.md | 2 +- .../dev/reports/playwright_structure_audit.md | 2 +- .../reports/playwright_v8_coverage_report.txt | 8 +- .../dev/reports/retry_suppression_report.md | 2 +- .../dev/reports/slow_path_pruning_report.md | 14 +- .../dev/reports/static_validation_report.md | 4 +- .../reports/targeted_file_manifest_report.md | 4 +- .../test_cleanup_performance_report.md | 16 +- .../reports/test_cleanup_routing_report.md | 2 +- .../reports/testing_lane_execution_report.md | 18 +- .../dev/reports/validation_cache_report.md | 30 +- .../reports/zero_browser_preflight_report.md | 2 +- ...031-idea-board-inline-tree-grid-actions.md | 14 + ...031-idea-board-inline-tree-grid-actions.md | 48 + ...031-idea-board-inline-tree-grid-actions.md | 29 + .../tools/IdeaBoardTableNotes.spec.mjs | 108 +- .../tools/ToolboxRoutePages.spec.mjs | 25 +- toolbox/idea-board/index.html | 55 +- toolbox/idea-board/index.js | 412 +- 42 files changed, 1966 insertions(+), 2656 deletions(-) create mode 100644 docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md create mode 100644 docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md create mode 100644 docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md diff --git a/docs_build/dev/codex_commands.md b/docs_build/dev/codex_commands.md index b10ca5027..2a6187c19 100644 --- a/docs_build/dev/codex_commands.md +++ b/docs_build/dev/codex_commands.md @@ -1,23 +1,39 @@ # Codex Commands -- Read skill: repo-build. -- Read skill: github:yeet. -- Read source of truth: docs_build/dev/PROJECT_INSTRUCTIONS.md. -- Re-read original Idea Board request from prior conversation and docs_build/pr/BUILD_PR_26170_002-idea-board-tool.md. -- Inspected toolbox/_tool_template-v2/index.html. -- Inspected toolbox/idea-board/index.html and toolbox/idea-board/index.js. -- Inspected tests/playwright/tools/IdeaBoardTableNotes.spec.mjs. -- Inspected tests/playwright/tools/ToolboxRoutePages.spec.mjs. -- Inspected tests/playwright/tools/RootToolsFutureState.spec.mjs. -- git fetch origin main --prune. -- git pull --ff-only origin main. -- git switch -c codex/fix-idea-board-pr-workflow. -- node --check toolbox/idea-board/index.js. -- node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs. -- node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs. -- node --check tests/playwright/tools/RootToolsFutureState.spec.mjs. -- Inline HTML guard for toolbox/idea-board/index.html. -- npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line. -- npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --project=playwright --workers=1 --reporter=line -g "Idea Board launches". -- npm run test:workspace-v2. -- git diff --check. +## PR +- `PR_26171_031-idea-board-inline-tree-grid-actions` + +## Source Documents +- `docs_build/dev/PROJECT_INSTRUCTIONS.md` +- `docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md` +- `docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md` +- `docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md` + +## Git Workflow Commands +- `git branch --show-current` +- `git status -sb` +- `git branch --list` +- `git pull --ff-only origin main` +- `git switch -c codex/pr-26171-031-idea-board-inline-tree-grid-actions` + +## Inspection Commands +- `Get-Content -Raw docs_build/dev/PROJECT_INSTRUCTIONS.md` +- `Get-Content -Raw toolbox/idea-board/index.html` +- `Get-Content -Raw toolbox/idea-board/index.js` +- `Get-Content -Raw tests/playwright/tools/IdeaBoardTableNotes.spec.mjs` +- `Get-Content -Raw tests/playwright/tools/ToolboxRoutePages.spec.mjs` + +## Validation Commands +- `node --check toolbox/idea-board/index.js` +- `node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs` +- `node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs` +- `node -e ""` +- `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line` +- `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --project=playwright --workers=1 --reporter=line -g "Idea Board launches"` +- `npm run test:workspace-v2` + +## Artifact Commands +- Generate `docs_build/dev/reports/codex_changed_files.txt` +- Generate `docs_build/dev/reports/codex_review.diff` +- Create `tmp/PR_26171_031-idea-board-inline-tree-grid-actions_delta.zip` +- Verify ZIP size and repo-relative paths diff --git a/docs_build/dev/commit_comment.txt b/docs_build/dev/commit_comment.txt index 82a8f44c3..564fbbd68 100644 --- a/docs_build/dev/commit_comment.txt +++ b/docs_build/dev/commit_comment.txt @@ -1,5 +1,5 @@ -PR_26171_030 idea board workflow fix +PR_26171_031 idea board inline tree grid actions -- fix Idea Board selected-idea notes context -- update targeted Playwright and workspace-v2 validation harness -- refresh Idea Board workflow docs and reports +- render selected idea notes directly under the idea row +- add inline idea and note add/edit/delete workflows +- update targeted Idea Board Playwright coverage and reports diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 4bd294215..af4805e30 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,11 +1,13 @@ -# Codex Changed Files - PR_26171_030-idea-board-workflow-fix +# Codex Changed Files - PR_26171_031-idea-board-inline-tree-grid-actions ## Git Workflow -- Branch: `codex/fix-idea-board-pr-workflow` -- Base: `origin/main` -- PR URL: https://github.com/ToolboxAid/HTML-JavaScript-Gaming/pull/4 -- Merge conflicts: resolved in `docs_build/dev/reports/codex_changed_files.txt` and `docs_build/dev/reports/codex_review.diff` by regenerating both reports from the final `origin/main` diff. -- Commit hash, push result, merge result, and final main commit: reported after commit, push, PR merge, and final main pull. +- Current branch: `codex/pr-26171-031-idea-board-inline-tree-grid-actions` +- Expected starting branch: `main` (PASS before branch creation). +- Created branch: `codex/pr-26171-031-idea-board-inline-tree-grid-actions`. +- Push result: pending until after commit. +- PR URL: pending until after push. +- Merge result: pending until after PR validation/merge. +- Final main commit: pending until after merge and final pull. ## Changed Files - docs_build/dev/codex_commands.md @@ -43,23 +45,10 @@ - docs_build/dev/reports/testing_lane_execution_report.md - docs_build/dev/reports/validation_cache_report.md - docs_build/dev/reports/zero_browser_preflight_report.md -- docs_build/pr/APPLY_PR_26171_026-idea-board-template-cleanup.md -- docs_build/pr/APPLY_PR_26171_027-idea-board-table-work-surface.md -- docs_build/pr/APPLY_PR_26171_028-idea-board-notes-table-governance.md -- docs_build/pr/APPLY_PR_26171_029-idea-board-validation-playwright.md -- docs_build/pr/APPLY_PR_26171_030-idea-board-workflow-fix.md -- docs_build/pr/BUILD_PR_26171_026-idea-board-template-cleanup.md -- docs_build/pr/BUILD_PR_26171_027-idea-board-table-work-surface.md -- docs_build/pr/BUILD_PR_26171_028-idea-board-notes-table-governance.md -- docs_build/pr/BUILD_PR_26171_029-idea-board-validation-playwright.md -- docs_build/pr/BUILD_PR_26171_030-idea-board-workflow-fix.md -- docs_build/pr/PLAN_PR_26171_026-idea-board-template-cleanup.md -- docs_build/pr/PLAN_PR_26171_027-idea-board-table-work-surface.md -- docs_build/pr/PLAN_PR_26171_028-idea-board-notes-table-governance.md -- docs_build/pr/PLAN_PR_26171_029-idea-board-validation-playwright.md -- docs_build/pr/PLAN_PR_26171_030-idea-board-workflow-fix.md +- docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md +- docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md +- docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md - tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -- tests/playwright/tools/RootToolsFutureState.spec.mjs - tests/playwright/tools/ToolboxRoutePages.spec.mjs - toolbox/idea-board/index.html - toolbox/idea-board/index.js @@ -100,46 +89,45 @@ - M docs_build/dev/reports/testing_lane_execution_report.md - M docs_build/dev/reports/validation_cache_report.md - M docs_build/dev/reports/zero_browser_preflight_report.md -- A docs_build/pr/APPLY_PR_26171_026-idea-board-template-cleanup.md -- A docs_build/pr/APPLY_PR_26171_027-idea-board-table-work-surface.md -- A docs_build/pr/APPLY_PR_26171_028-idea-board-notes-table-governance.md -- A docs_build/pr/APPLY_PR_26171_029-idea-board-validation-playwright.md -- A docs_build/pr/APPLY_PR_26171_030-idea-board-workflow-fix.md -- A docs_build/pr/BUILD_PR_26171_026-idea-board-template-cleanup.md -- A docs_build/pr/BUILD_PR_26171_027-idea-board-table-work-surface.md -- A docs_build/pr/BUILD_PR_26171_028-idea-board-notes-table-governance.md -- A docs_build/pr/BUILD_PR_26171_029-idea-board-validation-playwright.md -- A docs_build/pr/BUILD_PR_26171_030-idea-board-workflow-fix.md -- A docs_build/pr/PLAN_PR_26171_026-idea-board-template-cleanup.md -- A docs_build/pr/PLAN_PR_26171_027-idea-board-table-work-surface.md -- A docs_build/pr/PLAN_PR_26171_028-idea-board-notes-table-governance.md -- A docs_build/pr/PLAN_PR_26171_029-idea-board-validation-playwright.md -- A docs_build/pr/PLAN_PR_26171_030-idea-board-workflow-fix.md -- A tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -- M tests/playwright/tools/RootToolsFutureState.spec.mjs +- A docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md +- A docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md +- A docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md +- M tests/playwright/tools/IdeaBoardTableNotes.spec.mjs - M tests/playwright/tools/ToolboxRoutePages.spec.mjs - M toolbox/idea-board/index.html -- A toolbox/idea-board/index.js - -## Copied Files Verified -- PASS: Re-read `toolbox/_tool_template-v2/index.html`; verified Theme V2 stylesheet, header/footer partials, left/center/right `tool-workspace tool-workspace--wide` shell, accordion stack, ToolDisplayMode host, and external Theme V2 scripts. -- PASS: Verified `toolbox/_tool_template-v2` currently contains `index.html` only; there was no template-local JavaScript file to copy. -- PASS: Verified `toolbox/idea-board/index.html` adapts the template structure with Theme V2 CSS, header/footer partials, left and right tool columns, center panel, ToolDisplayMode host, accordions, and external scripts. -- PASS: Verified no inline ` - -+ - - - diff --git a/toolbox/idea-board/index.js b/toolbox/idea-board/index.js -new file mode 100644 -index 000000000..59424bf5b ---- /dev/null +index 59424bf5b..711506b77 100644 +--- a/toolbox/idea-board/index.js +++ b/toolbox/idea-board/index.js -@@ -0,0 +1,299 @@ -+const ideas = Object.freeze([ -+ { -+ id: "sky-orchard", -+ title: "Sky Orchard", -+ pitch: "Grow floating islands while defending them from storm creatures.", -+ status: "Exploring", -+ owner: "Creator", -+ updated: "2026-06-20", -+ }, -+ { -+ id: "clockwork-courier", -+ title: "Clockwork Courier", -+ pitch: "Deliver messages through looping city districts before time resets.", -+ status: "New", -+ owner: "Creator", -+ updated: "2026-06-20", -+ }, -+]); -+ -+const notesByIdea = new Map([ -+ [ -+ "sky-orchard", -+ [ -+ { -+ id: "sky-system-origin", -+ note: "System seed note: compare early Sky Orchard ideas before creating a project.", -+ type: "System", -+ createdBy: "System", -+ created: "2026-06-20", -+ updated: "2026-06-20", -+ system: true, -+ }, -+ { -+ id: "sky-creator-next-question", -+ note: "Ask whether the core loop is resource planning, action defense, or both.", -+ type: "Creator", -+ createdBy: "Creator", -+ created: "2026-06-20", -+ updated: "2026-06-20", -+ system: false, -+ }, -+ ], -+ ], -+ [ -+ "clockwork-courier", -+ [ -+ { -+ id: "clock-system-origin", -+ note: "System seed note: keep Clockwork Courier scoped until the time-loop hook is clear.", -+ type: "System", -+ createdBy: "System", -+ created: "2026-06-20", -+ updated: "2026-06-20", -+ system: true, -+ }, -+ { -+ id: "clock-creator-route-risk", -+ note: "Check whether district routing stays readable after the first reset.", -+ type: "Creator", -+ createdBy: "Creator", -+ created: "2026-06-20", -+ updated: "2026-06-20", -+ system: false, -+ }, -+ ], -+ ], -+]); -+ -+const state = { -+ selectedIdeaId: "sky-orchard", -+ editingNoteId: null, -+ addingNote: false, -+}; -+ -+function today() { -+ return new Date().toISOString().slice(0, 10); -+} -+ -+function selectedIdea() { -+ return ideas.find((idea) => idea.id === state.selectedIdeaId); -+} -+ -+function selectedNotes() { -+ return notesByIdea.get(state.selectedIdeaId); -+} -+ -+function cell(text) { -+ const td = document.createElement("td"); -+ td.textContent = text; -+ return td; -+} -+ -+function actionButton(label, action, variant = "") { -+ const control = document.createElement("button"); -+ control.className = variant ? `btn btn--compact ${variant}` : "btn btn--compact"; -+ control.type = "button"; -+ control.textContent = label; -+ control.dataset.ideaBoardAction = action; -+ return control; -+} -+ -+function updateStatus(root, message) { -+ const status = root.querySelector("[data-idea-board-status]"); -+ if (status) status.textContent = message; +@@ -1,10 +1,11 @@ +-const ideas = Object.freeze([ ++const statusOptions = Object.freeze(["New", "Exploring", "Parked", "Ready to Shape"]); ++ ++const ideas = [ + { + id: "sky-orchard", + title: "Sky Orchard", + pitch: "Grow floating islands while defending them from storm creatures.", + status: "Exploring", +- owner: "Creator", + updated: "2026-06-20", + }, + { +@@ -12,10 +13,9 @@ const ideas = Object.freeze([ + title: "Clockwork Courier", + pitch: "Deliver messages through looping city districts before time resets.", + status: "New", +- owner: "Creator", + updated: "2026-06-20", + }, +-]); ++]; + + const notesByIdea = new Map([ + [ +@@ -24,18 +24,12 @@ const notesByIdea = new Map([ + { + id: "sky-system-origin", + note: "System seed note: compare early Sky Orchard ideas before creating a project.", +- type: "System", +- createdBy: "System", +- created: "2026-06-20", + updated: "2026-06-20", + system: true, + }, + { + id: "sky-creator-next-question", + note: "Ask whether the core loop is resource planning, action defense, or both.", +- type: "Creator", +- createdBy: "Creator", +- created: "2026-06-20", + updated: "2026-06-20", + system: false, + }, +@@ -47,18 +41,12 @@ const notesByIdea = new Map([ + { + id: "clock-system-origin", + note: "System seed note: keep Clockwork Courier scoped until the time-loop hook is clear.", +- type: "System", +- createdBy: "System", +- created: "2026-06-20", + updated: "2026-06-20", + system: true, + }, + { + id: "clock-creator-route-risk", + note: "Check whether district routing stays readable after the first reset.", +- type: "Creator", +- createdBy: "Creator", +- created: "2026-06-20", + updated: "2026-06-20", + system: false, + }, +@@ -68,7 +56,9 @@ const notesByIdea = new Map([ + + const state = { + selectedIdeaId: "sky-orchard", ++ editingIdeaId: null, + editingNoteId: null, ++ addingIdea: false, + addingNote: false, + }; + +@@ -77,11 +67,17 @@ function today() { + } + + function selectedIdea() { +- return ideas.find((idea) => idea.id === state.selectedIdeaId); ++ return ideas.find((idea) => idea.id === state.selectedIdeaId) || null; +} + -+function renderIdeaSelection(root) { -+ for (const row of root.querySelectorAll("[data-idea-board-idea-row]")) { -+ const rowSelected = row.dataset.ideaBoardIdeaRow === state.selectedIdeaId; -+ row.setAttribute("aria-selected", String(rowSelected)); -+ const selectButton = row.querySelector("[data-idea-board-select-idea]"); -+ if (selectButton) { -+ selectButton.disabled = rowSelected; -+ selectButton.classList.toggle("primary", rowSelected); -+ selectButton.textContent = rowSelected ? "Selected" : "Select"; -+ } -+ } ++function notesForIdea(ideaId) { ++ if (!notesByIdea.has(ideaId)) notesByIdea.set(ideaId, []); ++ return notesByIdea.get(ideaId); + } + +-function selectedNotes() { +- return notesByIdea.get(state.selectedIdeaId); ++function noteCountLabel(ideaId) { ++ const count = notesForIdea(ideaId).length; ++ return `${count} ${count === 1 ? "Note" : "Notes"}`; + } + + function cell(text) { +@@ -90,70 +86,128 @@ function cell(text) { + return td; + } + +-function actionButton(label, action, variant = "") { ++function actionButton(label, action, datasetName, variant = "") { + const control = document.createElement("button"); + control.className = variant ? `btn btn--compact ${variant}` : "btn btn--compact"; + control.type = "button"; + control.textContent = label; +- control.dataset.ideaBoardAction = action; ++ control.dataset[datasetName] = action; + return control; + } + ++function textInput(label, value = "") { ++ const input = document.createElement("input"); ++ input.type = "text"; ++ input.value = value; ++ input.setAttribute("aria-label", label); ++ return input; +} + -+function renderSelectedIdeaContext(root) { -+ const idea = selectedIdea(); -+ if (!idea) { -+ updateStatus(root, `Idea Board cannot find selected idea: ${state.selectedIdeaId}.`); -+ return; -+ } -+ const title = root.querySelector("[data-idea-board-selected-title]"); -+ const summary = root.querySelector("[data-idea-board-selected-summary]"); -+ const caption = root.querySelector("[data-idea-board-notes-caption]"); -+ if (title) title.textContent = `Notes for ${idea.title}`; -+ if (summary) { -+ summary.textContent = `Selected idea context: ${idea.status}, owned by ${idea.owner}, updated ${idea.updated}.`; ++function statusSelect(value) { ++ const select = document.createElement("select"); ++ select.setAttribute("aria-label", "Idea status"); ++ select.dataset.ideaBoardIdeaStatusInput = "true"; ++ for (const optionValue of statusOptions) { ++ const option = document.createElement("option"); ++ option.value = optionValue; ++ option.textContent = optionValue; ++ option.selected = optionValue === value; ++ select.append(option); + } -+ if (caption) caption.textContent = `Selected idea notes for ${idea.title}`; ++ return select; +} + -+function renderInputRow(tbody, note = null) { + function updateStatus(root, message) { + const status = root.querySelector("[data-idea-board-status]"); + if (status) status.textContent = message; + } + +-function renderIdeaSelection(root) { +- for (const row of root.querySelectorAll("[data-idea-board-idea-row]")) { +- const rowSelected = row.dataset.ideaBoardIdeaRow === state.selectedIdeaId; +- row.setAttribute("aria-selected", String(rowSelected)); +- const selectButton = row.querySelector("[data-idea-board-select-idea]"); +- if (selectButton) { +- selectButton.disabled = rowSelected; +- selectButton.classList.toggle("primary", rowSelected); +- selectButton.textContent = rowSelected ? "Selected" : "Select"; +- } +- } ++function renderIdeaInputRow(tbody, idea = null) { + const row = document.createElement("tr"); -+ row.dataset.ideaBoardInlineInputRow = "true"; -+ if (note) row.dataset.noteId = note.id; -+ -+ const noteCell = document.createElement("td"); -+ const input = document.createElement("input"); -+ input.type = "text"; -+ input.value = note?.note || ""; -+ input.setAttribute("aria-label", note ? "Edit selected idea note text" : "New selected idea note text"); -+ input.dataset.ideaBoardNoteInput = "true"; -+ noteCell.append(input); -+ row.append(noteCell); -+ -+ row.append(cell(note?.type || "Creator")); -+ row.append(cell(note?.createdBy || "Creator")); -+ row.append(cell(note?.created || today())); -+ row.append(cell(today())); ++ row.dataset.ideaBoardIdeaInputRow = "true"; ++ if (idea) row.dataset.ideaId = idea.id; ++ ++ const titleCell = document.createElement("th"); ++ titleCell.scope = "row"; ++ const titleInput = textInput(idea ? "Edit idea title" : "New idea title", idea?.title || ""); ++ titleInput.dataset.ideaBoardIdeaTitleInput = "true"; ++ titleCell.append(titleInput); ++ row.append(titleCell); ++ ++ const pitchCell = document.createElement("td"); ++ const pitchInput = textInput(idea ? "Edit idea pitch" : "New idea pitch", idea?.pitch || ""); ++ pitchInput.dataset.ideaBoardIdeaPitchInput = "true"; ++ pitchCell.append(pitchInput); ++ row.append(pitchCell); ++ ++ const statusCell = document.createElement("td"); ++ statusCell.append(statusSelect(idea?.status || "New")); ++ row.append(statusCell); ++ row.append(cell(idea?.updated || today())); ++ row.append(cell(idea ? noteCountLabel(idea.id) : "0 Notes")); + + const actions = document.createElement("td"); -+ actions.append(actionButton("Save", "save", "primary"), " ", actionButton("Cancel", "cancel")); ++ actions.append( ++ actionButton("Save", "save", "ideaBoardIdeaAction", "primary"), ++ " ", ++ actionButton("Cancel", "cancel", "ideaBoardIdeaAction"), ++ ); + row.append(actions); + tbody.append(row); -+ input.focus(); -+} -+ -+function renderNoteRow(tbody, note) { ++ titleInput.focus(); + } + +-function renderSelectedIdeaContext(root) { +- const idea = selectedIdea(); +- if (!idea) { +- updateStatus(root, `Idea Board cannot find selected idea: ${state.selectedIdeaId}.`); +- return; +- } +- const title = root.querySelector("[data-idea-board-selected-title]"); +- const summary = root.querySelector("[data-idea-board-selected-summary]"); +- const caption = root.querySelector("[data-idea-board-notes-caption]"); +- if (title) title.textContent = `Notes for ${idea.title}`; +- if (summary) { +- summary.textContent = `Selected idea context: ${idea.status}, owned by ${idea.owner}, updated ${idea.updated}.`; +- } +- if (caption) caption.textContent = `Selected idea notes for ${idea.title}`; ++function renderIdeaRow(tbody, idea) { + const row = document.createElement("tr"); -+ row.dataset.noteId = note.id; -+ if (note.system) row.dataset.ideaBoardSystemNote = "true"; -+ row.append(cell(note.note)); -+ row.append(cell(note.type)); -+ row.append(cell(note.createdBy)); -+ row.append(cell(note.created)); -+ row.append(cell(note.updated)); ++ const selected = idea.id === state.selectedIdeaId; ++ row.dataset.ideaBoardIdeaRow = idea.id; ++ row.setAttribute("aria-selected", String(selected)); ++ ++ const title = document.createElement("th"); ++ title.scope = "row"; ++ title.textContent = idea.title; ++ row.append(title); ++ row.append(cell(idea.pitch)); ++ row.append(cell(idea.status)); ++ row.append(cell(idea.updated)); ++ ++ const notes = document.createElement("td"); ++ const notesButton = document.createElement("button"); ++ notesButton.className = selected ? "btn btn--compact primary" : "btn btn--compact"; ++ notesButton.type = "button"; ++ notesButton.textContent = noteCountLabel(idea.id); ++ notesButton.dataset.ideaBoardSelectIdea = idea.id; ++ notesButton.setAttribute("aria-expanded", String(selected)); ++ notes.append(notesButton); ++ row.append(notes); + + const actions = document.createElement("td"); -+ if (note.system) { -+ actions.textContent = "System locked"; -+ } else { -+ actions.append(actionButton("Edit", "edit"), " ", actionButton("Delete", "delete")); -+ } ++ actions.append( ++ actionButton("Edit", "edit", "ideaBoardIdeaAction"), ++ " ", ++ actionButton("Delete", "delete", "ideaBoardIdeaAction"), ++ ); + row.append(actions); + tbody.append(row); -+} + } + +-function renderInputRow(tbody, note = null) { ++function renderNoteInputRow(tbody, note = null) { + const row = document.createElement("tr"); +- row.dataset.ideaBoardInlineInputRow = "true"; ++ row.dataset.ideaBoardNoteInputRow = "true"; + if (note) row.dataset.noteId = note.id; + + const noteCell = document.createElement("td"); +- const input = document.createElement("input"); +- input.type = "text"; +- input.value = note?.note || ""; +- input.setAttribute("aria-label", note ? "Edit selected idea note text" : "New selected idea note text"); ++ const input = textInput(note ? "Edit note text" : "New note text", note?.note || ""); + input.dataset.ideaBoardNoteInput = "true"; + noteCell.append(input); + row.append(noteCell); + +- row.append(cell(note?.type || "Creator")); +- row.append(cell(note?.createdBy || "Creator")); +- row.append(cell(note?.created || today())); +- row.append(cell(today())); +- + const actions = document.createElement("td"); +- actions.append(actionButton("Save", "save", "primary"), " ", actionButton("Cancel", "cancel")); ++ actions.append( ++ actionButton("Save", "save", "ideaBoardNoteAction", "primary"), ++ " ", ++ actionButton("Cancel", "cancel", "ideaBoardNoteAction"), ++ ); + row.append(actions); + tbody.append(row); + input.focus(); +@@ -164,46 +218,116 @@ function renderNoteRow(tbody, note) { + row.dataset.noteId = note.id; + if (note.system) row.dataset.ideaBoardSystemNote = "true"; + row.append(cell(note.note)); +- row.append(cell(note.type)); +- row.append(cell(note.createdBy)); +- row.append(cell(note.created)); +- row.append(cell(note.updated)); + + const actions = document.createElement("td"); +- if (note.system) { +- actions.textContent = "System locked"; +- } else { +- actions.append(actionButton("Edit", "edit"), " ", actionButton("Delete", "delete")); ++ actions.append(actionButton("Edit", "edit", "ideaBoardNoteAction")); ++ if (!note.system) { ++ actions.append(" ", actionButton("Delete", "delete", "ideaBoardNoteAction")); + } + row.append(actions); + tbody.append(row); + } + +-function renderNotes(root) { +- const tbody = root.querySelector("[data-idea-board-notes-body]"); +- const notes = selectedNotes(); +- if (!tbody || !notes) { +- updateStatus(root, `Idea Board cannot load notes for selected idea: ${state.selectedIdeaId}.`); +- return; +- } +- tbody.replaceChildren(); +- if (state.addingNote) renderInputRow(tbody); +- for (const note of notes) { ++function renderExpandedNotesRow(tbody, idea) { ++ const row = document.createElement("tr"); ++ row.dataset.ideaBoardExpandedRow = idea.id; ++ ++ const content = document.createElement("td"); ++ content.colSpan = 6; ++ ++ const wrapper = document.createElement("div"); ++ wrapper.className = "content-stack"; ++ ++ const tableWrapper = document.createElement("div"); ++ tableWrapper.className = "table-wrapper"; ++ const notesTable = document.createElement("table"); ++ notesTable.className = "data-table data-table--fixed"; ++ notesTable.dataset.ideaBoardNotesTable = idea.id; ++ notesTable.setAttribute("aria-label", `${idea.title} notes`); ++ notesTable.innerHTML = "NoteActions"; ++ ++ const notesBody = document.createElement("tbody"); ++ notesBody.dataset.ideaBoardNotesBody = idea.id; ++ notesTable.append(notesBody); ++ tableWrapper.append(notesTable); ++ wrapper.append(tableWrapper); ++ ++ const controls = document.createElement("div"); ++ controls.className = "action-group"; ++ const addNote = actionButton("Add Note", "add", "ideaBoardNoteAction", "primary"); ++ addNote.dataset.ideaBoardAddNote = idea.id; ++ controls.append(addNote); ++ wrapper.append(controls); ++ ++ content.append(wrapper); ++ row.append(content); ++ tbody.append(row); + -+function renderNotes(root) { -+ const tbody = root.querySelector("[data-idea-board-notes-body]"); -+ const notes = selectedNotes(); -+ if (!tbody || !notes) { -+ updateStatus(root, `Idea Board cannot load notes for selected idea: ${state.selectedIdeaId}.`); -+ return; -+ } ++ if (state.addingNote) renderNoteInputRow(notesBody); ++ for (const note of notesForIdea(idea.id)) { + if (state.editingNoteId === note.id) { +- renderInputRow(tbody, note); ++ renderNoteInputRow(notesBody, note); + } else { +- renderNoteRow(tbody, note); ++ renderNoteRow(notesBody, note); + } + } + } + + function render(root) { +- renderIdeaSelection(root); +- renderSelectedIdeaContext(root); +- renderNotes(root); ++ const tbody = root.querySelector("[data-idea-board-ideas-body]"); ++ if (!tbody) return; + tbody.replaceChildren(); -+ if (state.addingNote) renderInputRow(tbody); -+ for (const note of notes) { -+ if (state.editingNoteId === note.id) { -+ renderInputRow(tbody, note); ++ for (const idea of ideas) { ++ if (state.editingIdeaId === idea.id) { ++ renderIdeaInputRow(tbody, idea); + } else { -+ renderNoteRow(tbody, note); ++ renderIdeaRow(tbody, idea); + } ++ if (state.selectedIdeaId === idea.id) renderExpandedNotesRow(tbody, idea); + } ++ if (state.addingIdea) renderIdeaInputRow(tbody); +} + -+function render(root) { -+ renderIdeaSelection(root); -+ renderSelectedIdeaContext(root); -+ renderNotes(root); -+} -+ -+function saveRow(root, row) { -+ const input = row.querySelector("[data-idea-board-note-input]"); -+ const value = input?.value.trim(); -+ if (!value) { -+ updateStatus(root, "Enter note text before saving."); -+ return; -+ } -+ -+ const notes = selectedNotes(); -+ if (!notes) { -+ updateStatus(root, `Idea Board cannot save notes for selected idea: ${state.selectedIdeaId}.`); ++function slugifyTitle(title) { ++ const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); ++ return slug || `idea-${Date.now()}`; + } + +-function saveRow(root, row) { ++function saveIdeaRow(root, row) { ++ const title = row.querySelector("[data-idea-board-idea-title-input]")?.value.trim(); ++ const pitch = row.querySelector("[data-idea-board-idea-pitch-input]")?.value.trim(); ++ const status = row.querySelector("[data-idea-board-idea-status-input]")?.value; ++ if (!title || !pitch || !status) { ++ updateStatus(root, "Enter an idea, pitch, and status before saving."); + return; + } + -+ const noteId = row.dataset.noteId; -+ if (noteId) { -+ const note = notes.find((item) => item.id === noteId && !item.system); -+ if (!note) { -+ updateStatus(root, "Only creator notes can be edited."); ++ const ideaId = row.dataset.ideaId; ++ if (ideaId) { ++ const idea = ideas.find((item) => item.id === ideaId); ++ if (!idea) { ++ updateStatus(root, `Idea Board cannot find idea: ${ideaId}.`); + return; + } -+ note.note = value; -+ note.updated = today(); -+ state.editingNoteId = null; -+ updateStatus(root, `Updated note for ${selectedIdea().title}.`); ++ idea.title = title; ++ idea.pitch = pitch; ++ idea.status = status; ++ idea.updated = today(); ++ state.editingIdeaId = null; ++ updateStatus(root, `Updated ${idea.title}.`); + } else { -+ notes.unshift({ -+ id: `creator-${state.selectedIdeaId}-${Date.now()}`, -+ note: value, -+ type: "Creator", -+ createdBy: "Creator", -+ created: today(), -+ updated: today(), -+ system: false, -+ }); -+ state.addingNote = false; -+ updateStatus(root, `Added note for ${selectedIdea().title}.`); ++ const baseId = slugifyTitle(title); ++ const id = ideas.some((idea) => idea.id === baseId) ? `${baseId}-${Date.now()}` : baseId; ++ ideas.push({ id, title, pitch, status, updated: today() }); ++ notesByIdea.set(id, []); ++ state.selectedIdeaId = id; ++ state.addingIdea = false; ++ updateStatus(root, `Added ${title}.`); + } + render(root); +} + -+function handleClick(root, event) { -+ const ideaButton = event.target.closest("[data-idea-board-select-idea]"); -+ if (ideaButton) { -+ state.selectedIdeaId = ideaButton.dataset.ideaBoardSelectIdea; -+ state.addingNote = false; -+ state.editingNoteId = null; -+ updateStatus(root, `Selected ${selectedIdea().title}. Notes now show that idea's context.`); -+ render(root); ++function saveNoteRow(root, row) { + const input = row.querySelector("[data-idea-board-note-input]"); + const value = input?.value.trim(); + if (!value) { +@@ -211,17 +335,12 @@ function saveRow(root, row) { + return; + } + +- const notes = selectedNotes(); +- if (!notes) { +- updateStatus(root, `Idea Board cannot save notes for selected idea: ${state.selectedIdeaId}.`); +- return; +- } +- ++ const notes = notesForIdea(state.selectedIdeaId); + const noteId = row.dataset.noteId; + if (noteId) { +- const note = notes.find((item) => item.id === noteId && !item.system); ++ const note = notes.find((item) => item.id === noteId); + if (!note) { +- updateStatus(root, "Only creator notes can be edited."); ++ updateStatus(root, `Idea Board cannot find note: ${noteId}.`); + return; + } + note.note = value; +@@ -230,11 +349,8 @@ function saveRow(root, row) { + updateStatus(root, `Updated note for ${selectedIdea().title}.`); + } else { + notes.unshift({ +- id: `creator-${state.selectedIdeaId}-${Date.now()}`, ++ id: `note-${state.selectedIdeaId}-${Date.now()}`, + note: value, +- type: "Creator", +- createdBy: "Creator", +- created: today(), + updated: today(), + system: false, + }); +@@ -244,51 +360,117 @@ function saveRow(root, row) { + render(root); + } + +-function handleClick(root, event) { +- const ideaButton = event.target.closest("[data-idea-board-select-idea]"); +- if (ideaButton) { +- state.selectedIdeaId = ideaButton.dataset.ideaBoardSelectIdea; +- state.addingNote = false; +- state.editingNoteId = null; +- updateStatus(root, `Selected ${selectedIdea().title}. Notes now show that idea's context.`); +- render(root); ++function selectIdea(root, ideaId) { ++ if (!ideas.some((idea) => idea.id === ideaId)) { ++ updateStatus(root, `Idea Board cannot select missing idea: ${ideaId}.`); + return; + } ++ state.selectedIdeaId = ideaId; ++ state.editingIdeaId = null; ++ state.editingNoteId = null; ++ state.addingNote = false; ++ updateStatus(root, `Expanded notes for ${selectedIdea().title}.`); ++ render(root); ++} + +- const addButton = event.target.closest("[data-idea-board-add-note]"); +- if (addButton) { +- state.addingNote = true; ++function deleteIdea(root, ideaId) { ++ const index = ideas.findIndex((idea) => idea.id === ideaId); ++ if (index < 0) { ++ updateStatus(root, `Idea Board cannot delete missing idea: ${ideaId}.`); + return; + } ++ const [removed] = ideas.splice(index, 1); ++ notesByIdea.delete(ideaId); ++ if (state.selectedIdeaId === ideaId) { ++ state.selectedIdeaId = ideas[Math.min(index, ideas.length - 1)]?.id || ""; + state.editingNoteId = null; +- updateStatus(root, `Adding a note for ${selectedIdea().title}.`); ++ state.addingNote = false; ++ } ++ updateStatus(root, `Deleted ${removed.title}.`); ++ render(root); ++} + -+ const addButton = event.target.closest("[data-idea-board-add-note]"); -+ if (addButton) { ++function handleIdeaAction(root, actionControl) { ++ const action = actionControl.dataset.ideaBoardIdeaAction; ++ const row = actionControl.closest("tr"); ++ const ideaId = actionControl.dataset.ideaBoardSelectIdea || row?.dataset.ideaBoardIdeaRow || row?.dataset.ideaId; ++ if (action === "select") { ++ selectIdea(root, ideaId); ++ } else if (action === "edit") { ++ state.editingIdeaId = ideaId; ++ state.addingIdea = false; ++ state.editingNoteId = null; ++ state.addingNote = false; ++ updateStatus(root, `Editing ${ideas.find((idea) => idea.id === ideaId)?.title}.`); + render(root); +- return; ++ } else if (action === "delete") { ++ deleteIdea(root, ideaId); ++ } else if (action === "cancel") { ++ state.editingIdeaId = null; ++ state.addingIdea = false; ++ updateStatus(root, "Cancelled idea edit."); ++ render(root); ++ } else if (action === "save") { ++ saveIdeaRow(root, row); + } ++} + +- const actionControl = event.target.closest("[data-idea-board-action]"); +- if (!actionControl) return; ++function handleNoteAction(root, actionControl) { ++ const action = actionControl.dataset.ideaBoardNoteAction; + const row = actionControl.closest("tr"); + const noteId = row?.dataset.noteId; +- const action = actionControl.dataset.ideaBoardAction; +- if (action === "edit") { ++ if (action === "add") { + state.addingNote = true; + state.editingNoteId = null; + updateStatus(root, `Adding a note for ${selectedIdea().title}.`); + render(root); ++ } else if (action === "edit") { + state.editingNoteId = noteId; + state.addingNote = false; + updateStatus(root, `Editing note for ${selectedIdea().title}.`); ++ render(root); + } else if (action === "delete") { +- const notes = selectedNotes(); +- const index = notes?.findIndex((note) => note.id === noteId && !note.system) ?? -1; ++ const notes = notesForIdea(state.selectedIdeaId); ++ const index = notes.findIndex((note) => note.id === noteId && !note.system); + if (index >= 0) { + notes.splice(index, 1); + updateStatus(root, `Deleted note for ${selectedIdea().title}.`); ++ render(root); + } + } else if (action === "cancel") { + state.editingNoteId = null; + state.addingNote = false; + updateStatus(root, `Cancelled note edit for ${selectedIdea().title}.`); ++ render(root); + } else if (action === "save") { +- saveRow(root, row); ++ saveNoteRow(root, row); ++ } ++} ++ ++function handleClick(root, event) { ++ const addIdea = event.target.closest("[data-idea-board-add-idea]"); ++ if (addIdea) { ++ state.addingIdea = true; ++ state.editingIdeaId = null; ++ updateStatus(root, "Adding a new idea."); ++ render(root); + return; + } +- render(root); ++ ++ const selectIdeaButton = event.target.closest("[data-idea-board-select-idea]"); ++ if (selectIdeaButton) { ++ selectIdea(root, selectIdeaButton.dataset.ideaBoardSelectIdea); + return; + } + -+ const actionControl = event.target.closest("[data-idea-board-action]"); -+ if (!actionControl) return; -+ const row = actionControl.closest("tr"); -+ const noteId = row?.dataset.noteId; -+ const action = actionControl.dataset.ideaBoardAction; -+ if (action === "edit") { -+ state.editingNoteId = noteId; -+ state.addingNote = false; -+ updateStatus(root, `Editing note for ${selectedIdea().title}.`); -+ } else if (action === "delete") { -+ const notes = selectedNotes(); -+ const index = notes?.findIndex((note) => note.id === noteId && !note.system) ?? -1; -+ if (index >= 0) { -+ notes.splice(index, 1); -+ updateStatus(root, `Deleted note for ${selectedIdea().title}.`); -+ } -+ } else if (action === "cancel") { -+ state.editingNoteId = null; -+ state.addingNote = false; -+ updateStatus(root, `Cancelled note edit for ${selectedIdea().title}.`); -+ } else if (action === "save") { -+ saveRow(root, row); ++ const ideaAction = event.target.closest("[data-idea-board-idea-action]"); ++ if (ideaAction) { ++ handleIdeaAction(root, ideaAction); + return; + } -+ render(root); -+} + -+document.addEventListener("DOMContentLoaded", () => { -+ const root = document.querySelector("[data-idea-board]"); -+ if (!root) return; -+ render(root); -+ root.addEventListener("click", (event) => handleClick(root, event)); -+}); ++ const noteAction = event.target.closest("[data-idea-board-note-action]"); ++ if (noteAction) handleNoteAction(root, noteAction); + } + + document.addEventListener("DOMContentLoaded", () => { diff --git a/docs_build/dev/reports/coverage_changed_js_guardrail.txt b/docs_build/dev/reports/coverage_changed_js_guardrail.txt index 7b1c51f19..e4434cc48 100644 --- a/docs_build/dev/reports/coverage_changed_js_guardrail.txt +++ b/docs_build/dev/reports/coverage_changed_js_guardrail.txt @@ -6,7 +6,7 @@ Missing changed runtime JS files are WARN, not FAIL. Source: Playwright/Chromium built-in V8 coverage from the active Playwright run. Changed runtime JS files considered: -(100%) none changed - no changed runtime JS files +(0%) toolbox/idea-board/index.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only Guardrail warnings: -(100%) none changed - no changed runtime JS files +(0%) toolbox/idea-board/index.js - WARNING: changed runtime JS file missing from coverage; advisory only diff --git a/docs_build/dev/reports/dependency_gating_report.md b/docs_build/dev/reports/dependency_gating_report.md index 49a5f1867..7af58fcab 100644 --- a/docs_build/dev/reports/dependency_gating_report.md +++ b/docs_build/dev/reports/dependency_gating_report.md @@ -1,6 +1,6 @@ # Dependency Gating Report -Generated: 2026-06-20T05:44:40.304Z +Generated: 2026-06-20T06:06:50.123Z Status: PASS ## Gate Order diff --git a/docs_build/dev/reports/dependency_hydration_reuse_report.md b/docs_build/dev/reports/dependency_hydration_reuse_report.md index 3b69608e4..d012874ad 100644 --- a/docs_build/dev/reports/dependency_hydration_reuse_report.md +++ b/docs_build/dev/reports/dependency_hydration_reuse_report.md @@ -1,6 +1,6 @@ # Dependency Hydration Reuse Report -Generated: 2026-06-20T05:44:40.305Z +Generated: 2026-06-20T06:06:50.124Z Status: PASS ## Summary diff --git a/docs_build/dev/reports/execution_graph_reuse_report.md b/docs_build/dev/reports/execution_graph_reuse_report.md index da0a520c4..ba09cc24e 100644 --- a/docs_build/dev/reports/execution_graph_reuse_report.md +++ b/docs_build/dev/reports/execution_graph_reuse_report.md @@ -1,6 +1,6 @@ # Execution Graph Reuse Report -Generated: 2026-06-20T05:44:40.305Z +Generated: 2026-06-20T06:06:50.124Z Status: PASS ## Summary @@ -16,7 +16,7 @@ Prevented targeted scheduling work: 0 | Lane | Status | Snapshot Status | Execution Graph Hash | Reason | | --- | --- | --- | --- | --- | -| workspace-contract | INVALIDATED | INVALIDATED | 6757c88f8a32946d | Lane snapshot is part of the selected targeted execution graph. | +| workspace-contract | INVALIDATED | INVALIDATED | 252760eee0f3dab4 | Lane snapshot is part of the selected targeted execution graph. | ## Safeguards diff --git a/docs_build/dev/reports/failure_fingerprint_report.md b/docs_build/dev/reports/failure_fingerprint_report.md index 9179b5709..61b595d34 100644 --- a/docs_build/dev/reports/failure_fingerprint_report.md +++ b/docs_build/dev/reports/failure_fingerprint_report.md @@ -1,6 +1,6 @@ # Failure Fingerprint Report -Generated: 2026-06-20T05:45:42.507Z +Generated: 2026-06-20T06:07:57.208Z Status: PASS ## Summary diff --git a/docs_build/dev/reports/filesystem_scan_reduction_report.md b/docs_build/dev/reports/filesystem_scan_reduction_report.md index 7f57d454e..34ba5359b 100644 --- a/docs_build/dev/reports/filesystem_scan_reduction_report.md +++ b/docs_build/dev/reports/filesystem_scan_reduction_report.md @@ -1,6 +1,6 @@ # Filesystem Scan Reduction Report -Generated: 2026-06-20T05:44:40.265Z +Generated: 2026-06-20T06:06:50.100Z Status: PASS ## Scan Enforcement diff --git a/docs_build/dev/reports/incremental_validation_report.md b/docs_build/dev/reports/incremental_validation_report.md index 82bbdd422..06591738f 100644 --- a/docs_build/dev/reports/incremental_validation_report.md +++ b/docs_build/dev/reports/incremental_validation_report.md @@ -1,6 +1,6 @@ # Incremental Validation Report -Generated: 2026-06-20T05:44:40.307Z +Generated: 2026-06-20T06:06:50.125Z Status: PASS ## Reuse Summary @@ -18,7 +18,7 @@ Prevented fixture resolution passes: 0 | Lane | Decision | Invalidated By | Runtime Savings Observation | | --- | --- | --- | --- | -| workspace-contract | INVALIDATED | Persistent manifest dependency graph hash changed for workspace-contract.; Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | Manifest was regenerated or skipped; no reuse savings for this lane. | +| workspace-contract | INVALIDATED | Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | Manifest was regenerated or skipped; no reuse savings for this lane. | ## Invalidation Rules diff --git a/docs_build/dev/reports/lane_compilation_report.md b/docs_build/dev/reports/lane_compilation_report.md index 1d4d53418..b3c4486ba 100644 --- a/docs_build/dev/reports/lane_compilation_report.md +++ b/docs_build/dev/reports/lane_compilation_report.md @@ -1,6 +1,6 @@ # Lane Compilation Report -Generated: 2026-06-20T05:44:40.303Z +Generated: 2026-06-20T06:06:50.123Z Status: PASS ## Lane Graph diff --git a/docs_build/dev/reports/lane_deduplication_report.md b/docs_build/dev/reports/lane_deduplication_report.md index 473fb4f8d..1381d00a6 100644 --- a/docs_build/dev/reports/lane_deduplication_report.md +++ b/docs_build/dev/reports/lane_deduplication_report.md @@ -1,6 +1,6 @@ # Lane Deduplication Report -Generated: 2026-06-20T05:44:40.304Z +Generated: 2026-06-20T06:06:50.124Z Status: PASS ## Summary diff --git a/docs_build/dev/reports/lane_input_validation_report.md b/docs_build/dev/reports/lane_input_validation_report.md index d1f0ff8e5..2adc73f34 100644 --- a/docs_build/dev/reports/lane_input_validation_report.md +++ b/docs_build/dev/reports/lane_input_validation_report.md @@ -1,6 +1,6 @@ # Lane Input Validation Report -Generated: 2026-06-20T05:44:40.307Z +Generated: 2026-06-20T06:06:50.126Z Status: PASS ## Input Files diff --git a/docs_build/dev/reports/lane_manifests/workspace-contract.json b/docs_build/dev/reports/lane_manifests/workspace-contract.json index be4e484e3..3cb3984c0 100644 --- a/docs_build/dev/reports/lane_manifests/workspace-contract.json +++ b/docs_build/dev/reports/lane_manifests/workspace-contract.json @@ -11,7 +11,7 @@ "tests/helpers/playwrightStorageIsolation.mjs": "22604b3e338d2c4a", "tests/helpers/playwrightV8CoverageReporter.mjs": "a1b81069fef85fd6", "tests/helpers/workspaceV2CoverageReporter.mjs": "2cf6bcedc7e43c85", - "tests/playwright/tools/RootToolsFutureState.spec.mjs": "00b5e5d5872cc433" + "tests/playwright/tools/RootToolsFutureState.spec.mjs": "39da9b89bd793366" }, "fixtures": [], "helpers": [ @@ -30,15 +30,15 @@ "tests/helpers/playwrightV8CoverageReporter.mjs", "tests/helpers/workspaceV2CoverageReporter.mjs" ], - "inputHash": "7b6ea10bf1c6c8ab", + "inputHash": "4b520ec67663da30", "lane": "workspace-contract", "laneDefinitionHash": "c93cc3ce4d3672a0", - "manifestHash": "b1d93cf51c8a9dd5", + "manifestHash": "2974ef9fd87603c5", "ownership": "tools", "tests": [ "tests/playwright/tools/RootToolsFutureState.spec.mjs" ], "version": 1, - "generatedAt": "2026-06-20T05:44:38.843Z", + "generatedAt": "2026-06-20T06:06:49.056Z", "source": "generated" } diff --git a/docs_build/dev/reports/lane_runtime_optimization_report.md b/docs_build/dev/reports/lane_runtime_optimization_report.md index 082a2a78a..e22d4dbe7 100644 --- a/docs_build/dev/reports/lane_runtime_optimization_report.md +++ b/docs_build/dev/reports/lane_runtime_optimization_report.md @@ -1,6 +1,6 @@ # Lane Runtime Optimization Report -Generated: 2026-06-20T05:44:40.304Z +Generated: 2026-06-20T06:06:50.124Z Status: PASS ## Runtime Cost Summary diff --git a/docs_build/dev/reports/lane_snapshot_report.md b/docs_build/dev/reports/lane_snapshot_report.md index 538dcfe7f..bf798861a 100644 --- a/docs_build/dev/reports/lane_snapshot_report.md +++ b/docs_build/dev/reports/lane_snapshot_report.md @@ -1,6 +1,6 @@ # Lane Snapshot Report -Generated: 2026-06-20T05:44:40.305Z +Generated: 2026-06-20T06:06:50.124Z Status: PASS Snapshot directory: docs_build/dev/reports/lane_snapshots @@ -17,7 +17,7 @@ Prevented manifest traversal: 0 | Lane | Status | Snapshot Path | Manifest Hash | Dependency Graph Hash | Helper Graph Hash | Fixture Graph Hash | Runtime Config Hash | Execution Graph Hash | Snapshot Hash | Reason | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_snapshots/workspace-contract.json | b1d93cf51c8a9dd5 | 7ddc1041bdb425d1 | 7d3db838f9f780e0 | 6c4fac7630b0b6f3 | 32fb60598a4cb1ee | 6757c88f8a32946d | 61cca917cea6be03 | Lane snapshot dependencyGraphHash changed for workspace-contract.; Lane snapshot executionGraphHash changed for workspace-contract.; Lane snapshot inputHash changed for workspace-contract.; Lane snapshot manifestHash changed for workspace-contract.; Lane snapshot snapshotHash changed for workspace-contract.; Lane snapshot warmStartHash changed for workspace-contract. | +| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_snapshots/workspace-contract.json | 2974ef9fd87603c5 | 7ddc1041bdb425d1 | 7d3db838f9f780e0 | 6c4fac7630b0b6f3 | 32fb60598a4cb1ee | 252760eee0f3dab4 | e0858e78c3c58246 | Lane snapshot executionGraphHash changed for workspace-contract.; Lane snapshot inputHash changed for workspace-contract.; Lane snapshot manifestHash changed for workspace-contract.; Lane snapshot snapshotHash changed for workspace-contract.; Lane snapshot warmStartHash changed for workspace-contract. | ## Snapshot Validation Findings diff --git a/docs_build/dev/reports/lane_snapshots/workspace-contract.json b/docs_build/dev/reports/lane_snapshots/workspace-contract.json index 33f41852b..ed9767a8d 100644 --- a/docs_build/dev/reports/lane_snapshots/workspace-contract.json +++ b/docs_build/dev/reports/lane_snapshots/workspace-contract.json @@ -26,7 +26,7 @@ ] }, "dependencyGraphHash": "7ddc1041bdb425d1", - "executionGraphHash": "6757c88f8a32946d", + "executionGraphHash": "252760eee0f3dab4", "fixtureGraph": { "fixtureHashes": {}, "fixtures": [] @@ -47,7 +47,7 @@ ] }, "helperGraphHash": "7d3db838f9f780e0", - "inputHash": "7b6ea10bf1c6c8ab", + "inputHash": "4b520ec67663da30", "lane": "workspace-contract", "laneCompilationStatus": "PASS", "laneDefinitionHash": "c93cc3ce4d3672a0", @@ -61,7 +61,7 @@ "tests/helpers/playwrightStorageIsolation.mjs": "22604b3e338d2c4a", "tests/helpers/playwrightV8CoverageReporter.mjs": "a1b81069fef85fd6", "tests/helpers/workspaceV2CoverageReporter.mjs": "2cf6bcedc7e43c85", - "tests/playwright/tools/RootToolsFutureState.spec.mjs": "00b5e5d5872cc433" + "tests/playwright/tools/RootToolsFutureState.spec.mjs": "39da9b89bd793366" }, "fixtures": [], "helpers": [ @@ -80,14 +80,14 @@ "tests/helpers/playwrightV8CoverageReporter.mjs", "tests/helpers/workspaceV2CoverageReporter.mjs" ], - "manifestHash": "b1d93cf51c8a9dd5", + "manifestHash": "2974ef9fd87603c5", "manifestPath": "docs_build/dev/reports/lane_manifests/workspace-contract.json", "source": "generated", "tests": [ "tests/playwright/tools/RootToolsFutureState.spec.mjs" ] }, - "manifestHash": "b1d93cf51c8a9dd5", + "manifestHash": "2974ef9fd87603c5", "ownership": "tools", "runtimeConfiguration": { "affectedSurface": "Root tools future-state navigation and Tool Template V2 contract", @@ -114,8 +114,8 @@ "requiresSamplesFlag": false }, "runtimeConfigurationHash": "32fb60598a4cb1ee", - "snapshotHash": "61cca917cea6be03", + "snapshotHash": "e0858e78c3c58246", "version": 1, - "warmStartHash": "20a97bb7208b833b", - "generatedAt": "2026-06-20T05:44:40.293Z" + "warmStartHash": "6a00e8e8eca552d3", + "generatedAt": "2026-06-20T06:06:50.119Z" } diff --git a/docs_build/dev/reports/lane_warm_start_report.md b/docs_build/dev/reports/lane_warm_start_report.md index b34070c86..393df432e 100644 --- a/docs_build/dev/reports/lane_warm_start_report.md +++ b/docs_build/dev/reports/lane_warm_start_report.md @@ -1,6 +1,6 @@ # Lane Warm-Start Report -Generated: 2026-06-20T05:44:40.305Z +Generated: 2026-06-20T06:06:50.124Z Status: PASS Warm-start directory: docs_build/dev/reports/lane_warm_starts @@ -17,7 +17,7 @@ Prevented lane graph assembly: 0 | Lane | Status | Warm-Start Path | Manifest Hash | Warm-Start Hash | Dependency Hydration Hash | Reason | | --- | --- | --- | --- | --- | --- | --- | -| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_warm_starts/workspace-contract.json | b1d93cf51c8a9dd5 | 20a97bb7208b833b | c2315ccbd4c09b9c | Warm-start dependencyGraphHash changed for workspace-contract.; Warm-start dependencyHydrationHash changed for workspace-contract.; Warm-start inputHash changed for workspace-contract.; Warm-start manifestHash changed for workspace-contract.; Warm-start warmStartHash changed for workspace-contract. | +| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_warm_starts/workspace-contract.json | 2974ef9fd87603c5 | 6a00e8e8eca552d3 | c2315ccbd4c09b9c | Warm-start inputHash changed for workspace-contract.; Warm-start manifestHash changed for workspace-contract.; Warm-start warmStartHash changed for workspace-contract. | ## Fast-Fail Safeguards diff --git a/docs_build/dev/reports/lane_warm_starts/workspace-contract.json b/docs_build/dev/reports/lane_warm_starts/workspace-contract.json index 8bedb337b..525610984 100644 --- a/docs_build/dev/reports/lane_warm_starts/workspace-contract.json +++ b/docs_build/dev/reports/lane_warm_starts/workspace-contract.json @@ -39,15 +39,15 @@ ] }, "dependencyHydrationHash": "c2315ccbd4c09b9c", - "inputHash": "7b6ea10bf1c6c8ab", + "inputHash": "4b520ec67663da30", "lane": "workspace-contract", "laneConfigHash": "990389bf77bad0d7", "laneDefinitionHash": "c93cc3ce4d3672a0", - "manifestHash": "b1d93cf51c8a9dd5", + "manifestHash": "2974ef9fd87603c5", "ownership": "tools", - "warmStartHash": "20a97bb7208b833b", + "warmStartHash": "6a00e8e8eca552d3", "version": 1, - "generatedAt": "2026-06-20T05:44:38.862Z", + "generatedAt": "2026-06-20T06:06:49.066Z", "manifestPath": "docs_build/dev/reports/lane_manifests/workspace-contract.json", "sourceManifest": "generated" } diff --git a/docs_build/dev/reports/monolith_trigger_removal_report.md b/docs_build/dev/reports/monolith_trigger_removal_report.md index d10053b47..846465261 100644 --- a/docs_build/dev/reports/monolith_trigger_removal_report.md +++ b/docs_build/dev/reports/monolith_trigger_removal_report.md @@ -1,6 +1,6 @@ # Monolith Trigger Removal Report -Generated: 2026-06-20T05:45:42.508Z +Generated: 2026-06-20T06:07:57.210Z Status: PASS ## Removed Broad Execution Triggers diff --git a/docs_build/dev/reports/persistent_lane_manifest_report.md b/docs_build/dev/reports/persistent_lane_manifest_report.md index 13159be30..73dea4c16 100644 --- a/docs_build/dev/reports/persistent_lane_manifest_report.md +++ b/docs_build/dev/reports/persistent_lane_manifest_report.md @@ -1,6 +1,6 @@ # Persistent Lane Manifest Report -Generated: 2026-06-20T05:44:40.307Z +Generated: 2026-06-20T06:06:50.125Z Status: PASS Manifest directory: docs_build/dev/reports/lane_manifests @@ -15,13 +15,13 @@ Prevented discovery scans: 0 | Lane | Status | Manifest Path | Input Hash | Manifest Hash | Reason | | --- | --- | --- | --- | --- | --- | -| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_manifests/workspace-contract.json | 7b6ea10bf1c6c8ab | b1d93cf51c8a9dd5 | Persistent manifest dependency graph hash changed for workspace-contract.; Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | +| workspace-contract | INVALIDATED | docs_build/dev/reports/lane_manifests/workspace-contract.json | 4b520ec67663da30 | 2974ef9fd87603c5 | Persistent manifest input hash changed for workspace-contract.; Persistent manifest hash changed for workspace-contract. | ## Persisted Manifest Files | Lane | Ownership | Source | Tests | Helpers | Fixtures | Dependency Graph Hash | Manifest Hash | | --- | --- | --- | --- | --- | --- | --- | --- | -| workspace-contract | tools | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | 7ddc1041bdb425d1 | b1d93cf51c8a9dd5 | +| workspace-contract | tools | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | 7ddc1041bdb425d1 | 2974ef9fd87603c5 | ## Fast-Fail Enforcement diff --git a/docs_build/dev/reports/playwright_discovery_ownership_report.md b/docs_build/dev/reports/playwright_discovery_ownership_report.md index dcbb828ce..e907a0113 100644 --- a/docs_build/dev/reports/playwright_discovery_ownership_report.md +++ b/docs_build/dev/reports/playwright_discovery_ownership_report.md @@ -1,6 +1,6 @@ # Playwright Discovery Ownership Report -Generated: 2026-06-20T05:44:40.256Z +Generated: 2026-06-20T06:06:50.093Z Status: PASS ## Discovery-Time Ownership diff --git a/docs_build/dev/reports/playwright_discovery_scope_report.md b/docs_build/dev/reports/playwright_discovery_scope_report.md index 18e9df5b9..c13f9a8ea 100644 --- a/docs_build/dev/reports/playwright_discovery_scope_report.md +++ b/docs_build/dev/reports/playwright_discovery_scope_report.md @@ -1,6 +1,6 @@ # Playwright Discovery Scope Report -Generated: 2026-06-20T05:44:40.260Z +Generated: 2026-06-20T06:06:50.098Z Status: PASS Scoped discovery: Yes diff --git a/docs_build/dev/reports/playwright_structure_audit.md b/docs_build/dev/reports/playwright_structure_audit.md index 0b2354d1d..e13e46c36 100644 --- a/docs_build/dev/reports/playwright_structure_audit.md +++ b/docs_build/dev/reports/playwright_structure_audit.md @@ -1,6 +1,6 @@ # Playwright Structure Audit -Generated: 2026-06-20T05:44:40.218Z +Generated: 2026-06-20T06:06:50.073Z Status: PASS ## Lane Directories diff --git a/docs_build/dev/reports/playwright_v8_coverage_report.txt b/docs_build/dev/reports/playwright_v8_coverage_report.txt index 7560c6da4..0ee7e11eb 100644 --- a/docs_build/dev/reports/playwright_v8_coverage_report.txt +++ b/docs_build/dev/reports/playwright_v8_coverage_report.txt @@ -17,7 +17,7 @@ Exercised tool entry points detected: (61%) Theme V2 Shared JS - exercised 7 runtime JS files Changed runtime JS files covered: -(100%) none changed - no changed runtime JS files +(0%) toolbox/idea-board/index.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only Files with executed line/function counts where available: (14%) assets/theme-v2/js/account-auth-service.js - executed lines 64/64; executed functions 1/7 @@ -48,7 +48,9 @@ Files with executed line/function counts where available: (100%) toolbox/game-workspace/game-workspace-api-client.js - executed lines 20/20; executed functions 3/3 Uncovered or low-coverage changed JS files: -(100%) none changed - no changed runtime JS files +(0%) toolbox/idea-board/index.js - WARNING: uncovered changed runtime JS file; advisory only Changed JS files considered: -(100%) none - no changed JS files +(0%) tests/playwright/tools/IdeaBoardTableNotes.spec.mjs - changed JS file not collected as browser runtime coverage +(0%) tests/playwright/tools/ToolboxRoutePages.spec.mjs - changed JS file not collected as browser runtime coverage +(0%) toolbox/idea-board/index.js - changed JS file not collected as browser runtime coverage diff --git a/docs_build/dev/reports/retry_suppression_report.md b/docs_build/dev/reports/retry_suppression_report.md index 9237bffd9..5b8011912 100644 --- a/docs_build/dev/reports/retry_suppression_report.md +++ b/docs_build/dev/reports/retry_suppression_report.md @@ -1,6 +1,6 @@ # Retry Suppression Report -Generated: 2026-06-20T05:45:42.508Z +Generated: 2026-06-20T06:07:57.209Z Status: PASS ## Summary diff --git a/docs_build/dev/reports/slow_path_pruning_report.md b/docs_build/dev/reports/slow_path_pruning_report.md index b218b11d0..23ca74691 100644 --- a/docs_build/dev/reports/slow_path_pruning_report.md +++ b/docs_build/dev/reports/slow_path_pruning_report.md @@ -1,13 +1,13 @@ # Slow Path Pruning Report -Generated: 2026-06-20T05:45:42.508Z +Generated: 2026-06-20T06:07:57.210Z Status: PASS Source timing evidence: docs_build/dev/reports/test_cleanup_performance_report.md (2026-05-26T21:18:42.199Z) ## Before / After Runtime Observations PR_26146_038 measured lane elapsed time: 169.71s -Current measured lane elapsed time: 62.12s +Current measured lane elapsed time: 67.03s PR_26146_038 actual browser launches: 4 Current actual browser launches: 1 Accidental no-argument browser launches prevented: 5 @@ -31,11 +31,11 @@ Validation cache hits: 18 | PR_26146_038 | tool-runtime | 19.10s | Asset Manager V2 temporary UAT context | | PR_26146_038 | integration | 14.50s | games index resolves Pong thumbnail from manifest preview role | | PR_26146_038 | tool-runtime | 10.10s | Preview Generator V2 real batch output | -| current targeted run | workspace-contract | 14.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:662:1 > representative active tool pages align center cleanup and registry group colors | -| current targeted run | workspace-contract | 14.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | -| current targeted run | workspace-contract | 12.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:560:1 > learn wireframe pages load with shared Theme V2 structure | -| current targeted run | workspace-contract | 10.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:480:1 > common header renders primary navigation order across active pages | -| current targeted run | workspace-contract | 1.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:639:1 > tool template future-state page loads from root Theme V2 paths | +| current targeted run | workspace-contract | 16.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | +| current targeted run | workspace-contract | 14.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:662:1 > representative active tool pages align center cleanup and registry group colors | +| current targeted run | workspace-contract | 13.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:560:1 > learn wireframe pages load with shared Theme V2 structure | +| current targeted run | workspace-contract | 9.60s | tests\playwright\tools\RootToolsFutureState.spec.mjs:480:1 > common header renders primary navigation order across active pages | +| current targeted run | workspace-contract | 1.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:639:1 > tool template future-state page loads from root Theme V2 paths | ## Guardrails diff --git a/docs_build/dev/reports/static_validation_report.md b/docs_build/dev/reports/static_validation_report.md index dcf5dccb4..c2515050d 100644 --- a/docs_build/dev/reports/static_validation_report.md +++ b/docs_build/dev/reports/static_validation_report.md @@ -1,6 +1,6 @@ # Static Validation Report -Generated: 2026-06-20T05:44:40.289Z +Generated: 2026-06-20T06:06:50.116Z Status: PASS Static only: No Dry run: No @@ -22,7 +22,7 @@ Reason: No deterministic static validation failure was found. | invalid filename detection | PASS | Covered by Playwright structure audit. | | missing import detection | PASS | Covered by Playwright structure audit relative import checks. | | missing fixture detection | PASS | No missing fixture findings. | -| targeted file manifests | PASS | workspace-contract:b1d93cf51c8a9dd5 | +| targeted file manifests | PASS | workspace-contract:2974ef9fd87603c5 | | persistent lane manifests | PASS | workspace-contract:INVALIDATED | | lane warm-start reuse | PASS | workspace-contract:INVALIDATED | | dependency hydration reuse | PASS | workspace-contract:INVALIDATED | diff --git a/docs_build/dev/reports/targeted_file_manifest_report.md b/docs_build/dev/reports/targeted_file_manifest_report.md index 6f4baf704..e53f66539 100644 --- a/docs_build/dev/reports/targeted_file_manifest_report.md +++ b/docs_build/dev/reports/targeted_file_manifest_report.md @@ -1,13 +1,13 @@ # Targeted File Manifest Report -Generated: 2026-06-20T05:44:40.306Z +Generated: 2026-06-20T06:06:50.125Z Status: PASS ## Manifest-Generated Lane Inputs | Lane | Ownership | Status | Source | Tests | Helpers | Fixtures | Imports / Dependencies | Dependency Graph Hash | Manifest Hash | Reason | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | -| workspace-contract | tools | PASS | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | src/dev-runtime/admin/admin-notes-directory.mjs; src/dev-runtime/admin/admin-notes-menu.mjs; src/dev-runtime/persistence/mock-db-store.js; src/dev-runtime/server/local-api-router.mjs; tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | 7ddc1041bdb425d1 | b1d93cf51c8a9dd5 | Manifest ownership, helpers, fixtures, imports, and command targets are deterministic before runtime. | +| workspace-contract | tools | PASS | generated | tests/playwright/tools/RootToolsFutureState.spec.mjs | tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | none | src/dev-runtime/admin/admin-notes-directory.mjs; src/dev-runtime/admin/admin-notes-menu.mjs; src/dev-runtime/persistence/mock-db-store.js; src/dev-runtime/server/local-api-router.mjs; tests/helpers/playwrightRepoServer.mjs; tests/helpers/playwrightStorageIsolation.mjs; tests/helpers/playwrightV8CoverageReporter.mjs; tests/helpers/workspaceV2CoverageReporter.mjs | 7ddc1041bdb425d1 | 2974ef9fd87603c5 | Manifest ownership, helpers, fixtures, imports, and command targets are deterministic before runtime. | ## Discovery Expansion Control diff --git a/docs_build/dev/reports/test_cleanup_performance_report.md b/docs_build/dev/reports/test_cleanup_performance_report.md index 010dff785..e0fd9aa87 100644 --- a/docs_build/dev/reports/test_cleanup_performance_report.md +++ b/docs_build/dev/reports/test_cleanup_performance_report.md @@ -1,11 +1,11 @@ # Test Cleanup Performance Report -Generated: 2026-06-20T05:45:42.508Z +Generated: 2026-06-20T06:07:57.209Z Status: PASS ## Cost Summary -Total measured lane elapsed time: 62.12s +Total measured lane elapsed time: 67.03s Actual browser launch count: 1 Scheduled browser launch count: 1 Baseline browser launch count: 1 @@ -23,7 +23,7 @@ Prevented redundant dependency traversal: 0 | Lane | Status | Elapsed | Browser Launches | Reason | | --- | --- | --- | --- | --- | -| workspace-contract | PASS | 62.12s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | +| workspace-contract | PASS | 67.03s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | | game-workspace | SKIP | 0ms | 0 | Lane was not selected for this targeted run. | | game-design | SKIP | 0ms | 0 | Lane was not selected for this targeted run. | | game-configuration | SKIP | 0ms | 0 | Lane was not selected for this targeted run. | @@ -43,11 +43,11 @@ Prevented redundant dependency traversal: 0 | Lane | Duration | Test | Command | | --- | --- | --- | --- | -| workspace-contract | 14.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:662:1 > representative active tool pages align center cleanup and registry group colors | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -| workspace-contract | 14.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -| workspace-contract | 12.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:560:1 > learn wireframe pages load with shared Theme V2 structure | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -| workspace-contract | 10.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:480:1 > common header renders primary navigation order across active pages | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | -| workspace-contract | 1.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:639:1 > tool template future-state page loads from root Theme V2 paths | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +| workspace-contract | 16.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +| workspace-contract | 14.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:662:1 > representative active tool pages align center cleanup and registry group colors | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +| workspace-contract | 13.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:560:1 > learn wireframe pages load with shared Theme V2 structure | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +| workspace-contract | 9.60s | tests\playwright\tools\RootToolsFutureState.spec.mjs:480:1 > common header renders primary navigation order across active pages | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | +| workspace-contract | 1.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:639:1 > tool template future-state page loads from root Theme V2 paths | C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list | ## Prevented Broad Execution diff --git a/docs_build/dev/reports/test_cleanup_routing_report.md b/docs_build/dev/reports/test_cleanup_routing_report.md index 5f623e338..521937448 100644 --- a/docs_build/dev/reports/test_cleanup_routing_report.md +++ b/docs_build/dev/reports/test_cleanup_routing_report.md @@ -1,6 +1,6 @@ # Test Cleanup Routing Report -Generated: 2026-06-20T05:45:42.508Z +Generated: 2026-06-20T06:07:57.210Z Status: PASS ## Representative Routing Cases diff --git a/docs_build/dev/reports/testing_lane_execution_report.md b/docs_build/dev/reports/testing_lane_execution_report.md index cb83c373d..be0e2d686 100644 --- a/docs_build/dev/reports/testing_lane_execution_report.md +++ b/docs_build/dev/reports/testing_lane_execution_report.md @@ -1,6 +1,6 @@ # Testing Lane Execution Report -Generated: 2026-06-20T05:45:42.544Z +Generated: 2026-06-20T06:07:57.358Z Dry run: No ## Summary @@ -9,7 +9,7 @@ PASS: 1 WARN: 0 FAIL: 0 SKIP: 14 -Total lane elapsed time: 62.12s +Total lane elapsed time: 67.03s Actual browser launches: 1 ## Full Samples Smoke @@ -105,7 +105,7 @@ Prevented Workspace lane reruns: 0 | Lane | Status | Elapsed | Browser Launches | Executed/Skipped Reason | Affected Surface | Fixtures / Inputs | | --- | --- | --- | --- | --- | --- | --- | -| workspace-contract | PASS | 62.12s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | Root tools future-state navigation and Tool Template V2 contract | repo-served root tools page; Tool Template V2 future-state page; Theme V2 shared partials and assets | +| workspace-contract | PASS | 67.03s | 1 | Workspace V2 command now validates the future-state tools surface without exercising deprecated toolbox/old_* routes. | Root tools future-state navigation and Tool Template V2 contract | repo-served root tools page; Tool Template V2 future-state page; Theme V2 shared partials and assets | | game-workspace | SKIP | 0ms | 0 | Lane was not selected for this targeted run. | Game Workspace mock repository, Game Workspace UI, and Toolbox Progress/Build Path game-state bridge | repo-served Game Workspace page; repo-served Toolbox page with role simulation; in-memory SQL-shaped mock game repository | | game-design | SKIP | 0ms | 0 | Lane was not selected for this targeted run. | Game Design mock repository, project purpose flow, validation overlay, capability demo authoring, and Toolbox progress handoff | repo-served Game Design page; repo-served Toolbox Progress and Build Path views; in-memory SQL-shaped Game Design mock repository; Game Workspace mock game context | | game-configuration | SKIP | 0ms | 0 | Lane was not selected for this targeted run. | Game Configuration mock repository, Game Design handoff, configuration validation, user-facing output, and Toolbox progress handoff | repo-served Game Configuration page; repo-served Game Design page for handoff checks; repo-served Toolbox Progress and Build Path views; in-memory SQL-shaped Game Configuration mock repository; Game Design mock repository handoff | @@ -125,16 +125,16 @@ Prevented Workspace lane reruns: 0 | Lane | Duration | Test | | --- | --- | --- | -| workspace-contract | 14.10s | tests\playwright\tools\RootToolsFutureState.spec.mjs:662:1 > representative active tool pages align center cleanup and registry group colors | -| workspace-contract | 14.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | -| workspace-contract | 12.90s | tests\playwright\tools\RootToolsFutureState.spec.mjs:560:1 > learn wireframe pages load with shared Theme V2 structure | -| workspace-contract | 10.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:480:1 > common header renders primary navigation order across active pages | -| workspace-contract | 1.70s | tests\playwright\tools\RootToolsFutureState.spec.mjs:639:1 > tool template future-state page loads from root Theme V2 paths | +| workspace-contract | 16.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:270:1 > root tools surface links current tool pages without old_* routes | +| workspace-contract | 14.00s | tests\playwright\tools\RootToolsFutureState.spec.mjs:662:1 > representative active tool pages align center cleanup and registry group colors | +| workspace-contract | 13.30s | tests\playwright\tools\RootToolsFutureState.spec.mjs:560:1 > learn wireframe pages load with shared Theme V2 structure | +| workspace-contract | 9.60s | tests\playwright\tools\RootToolsFutureState.spec.mjs:480:1 > common header renders primary navigation order across active pages | +| workspace-contract | 1.80s | tests\playwright\tools\RootToolsFutureState.spec.mjs:639:1 > tool template future-state page loads from root Theme V2 paths | ## Commands ### workspace-contract -- PASS 62.12s C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list +- PASS 67.03s C:\nvm4w\nodejs\node.exe "C:\\Users\\davidq\\Documents\\GitHub\\HTML-JavaScript-Gaming - 2\\node_modules\\@playwright\\test\\cli.js" test tests/playwright/tools/RootToolsFutureState.spec.mjs --project=playwright --workers=1 --reporter=list ### game-workspace - SKIP diff --git a/docs_build/dev/reports/validation_cache_report.md b/docs_build/dev/reports/validation_cache_report.md index 0e34656e5..baece9001 100644 --- a/docs_build/dev/reports/validation_cache_report.md +++ b/docs_build/dev/reports/validation_cache_report.md @@ -1,6 +1,6 @@ # Validation Cache Report -Generated: 2026-06-20T05:44:40.309Z +Generated: 2026-06-20T06:06:50.127Z Status: PASS ## Cache Summary @@ -15,26 +15,26 @@ Validations computed: 10 | lane registration validation | MISS | 2e5779ed0ed947c7 | initial computation | lane definitions change; package.json lane scripts change | | runner preflight validation | MISS | 82ebdc0c4e1e7794 | initial computation | lane definitions change; fixture ownership changes; targeted files change | | scoped discovery map | MISS | 97bdfaa7c37c6c39 | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change | -| targeted file manifest validation | MISS | 569afc9ef4d65966 | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change | -| lane warm-start validation | MISS | ce69ece0978a145c | initial computation | lane definitions change; targeted files change; ownership metadata changes; dependency graph changes; helper/fixture placement changes; lane configuration changes | -| structural ownership validation | MISS | 7d16549767cccc93 | initial computation | fixture ownership changes; helper/import graph changes; targeted files change | +| targeted file manifest validation | MISS | 696efe659341aeab | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change | +| lane warm-start validation | MISS | fb3e2200afe07095 | initial computation | lane definitions change; targeted files change; ownership metadata changes; dependency graph changes; helper/fixture placement changes; lane configuration changes | +| structural ownership validation | MISS | 7637fdf8fc29545f | initial computation | fixture ownership changes; helper/import graph changes; targeted files change | | lane compilation validation | MISS | c974136a7964ae67 | initial computation | lane definitions change; targeted files change; fixture ownership changes | | lane compilation validation | HIT | c974136a7964ae67 | dependency validation input | unchanged within execution cycle | | dependency validation | MISS | 46fe936f65e8a29d | initial computation | dependency graph changes; lane definitions change; lane compilation input changes | -| lane snapshot validation | MISS | 34a15b2f8ce4e4f5 | initial computation | targeted files change; dependency graph changes; helper/fixture ownership changes; lane configuration changes; runtime configuration changes | +| lane snapshot validation | MISS | 3dbdd7a291b32661 | initial computation | targeted files change; dependency graph changes; helper/fixture ownership changes; lane configuration changes; runtime configuration changes | | zero-browser preflight | MISS | e92e4238c2719b6f | initial computation | lane definitions change; fixture ownership changes; helper/import graph changes; targeted files change; dependency graph changes | -| structural ownership validation | HIT | 7d16549767cccc93 | static validation report | unchanged within execution cycle | -| structural ownership validation | HIT | 7d16549767cccc93 | zero-browser preflight report | unchanged within execution cycle | +| structural ownership validation | HIT | 7637fdf8fc29545f | static validation report | unchanged within execution cycle | +| structural ownership validation | HIT | 7637fdf8fc29545f | zero-browser preflight report | unchanged within execution cycle | | scoped discovery map | HIT | 97bdfaa7c37c6c39 | structural ownership validation input | unchanged within execution cycle | | scoped discovery map | HIT | 97bdfaa7c37c6c39 | discovery scope reporting | unchanged within execution cycle | -| targeted file manifest validation | HIT | 569afc9ef4d65966 | lane input validation report | unchanged within execution cycle | -| targeted file manifest validation | HIT | 569afc9ef4d65966 | runtime scheduling blockers | unchanged within execution cycle | -| lane warm-start validation | HIT | ce69ece0978a145c | warm-start report | unchanged within execution cycle | -| lane warm-start validation | HIT | ce69ece0978a145c | dependency hydration reuse report | unchanged within execution cycle | -| lane warm-start validation | HIT | ce69ece0978a145c | runtime scheduling | unchanged within execution cycle | -| lane snapshot validation | HIT | 34a15b2f8ce4e4f5 | lane snapshot report | unchanged within execution cycle | -| lane snapshot validation | HIT | 34a15b2f8ce4e4f5 | execution graph reuse report | unchanged within execution cycle | -| lane snapshot validation | HIT | 34a15b2f8ce4e4f5 | runtime scheduling | unchanged within execution cycle | +| targeted file manifest validation | HIT | 696efe659341aeab | lane input validation report | unchanged within execution cycle | +| targeted file manifest validation | HIT | 696efe659341aeab | runtime scheduling blockers | unchanged within execution cycle | +| lane warm-start validation | HIT | fb3e2200afe07095 | warm-start report | unchanged within execution cycle | +| lane warm-start validation | HIT | fb3e2200afe07095 | dependency hydration reuse report | unchanged within execution cycle | +| lane warm-start validation | HIT | fb3e2200afe07095 | runtime scheduling | unchanged within execution cycle | +| lane snapshot validation | HIT | 3dbdd7a291b32661 | lane snapshot report | unchanged within execution cycle | +| lane snapshot validation | HIT | 3dbdd7a291b32661 | execution graph reuse report | unchanged within execution cycle | +| lane snapshot validation | HIT | 3dbdd7a291b32661 | runtime scheduling | unchanged within execution cycle | | lane compilation validation | HIT | c974136a7964ae67 | lane compilation report | unchanged within execution cycle | | lane compilation validation | HIT | c974136a7964ae67 | runtime scheduling | unchanged within execution cycle | | dependency validation | HIT | 46fe936f65e8a29d | dependency report | unchanged within execution cycle | diff --git a/docs_build/dev/reports/zero_browser_preflight_report.md b/docs_build/dev/reports/zero_browser_preflight_report.md index 810a3911b..283a446cd 100644 --- a/docs_build/dev/reports/zero_browser_preflight_report.md +++ b/docs_build/dev/reports/zero_browser_preflight_report.md @@ -1,6 +1,6 @@ # Zero-Browser Preflight Report -Generated: 2026-06-20T05:44:40.308Z +Generated: 2026-06-20T06:06:50.126Z Status: PASS ## Prevented Browser Launches diff --git a/docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md b/docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md new file mode 100644 index 000000000..11945bf2a --- /dev/null +++ b/docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md @@ -0,0 +1,14 @@ +# APPLY PR_26171_031-idea-board-inline-tree-grid-actions + +## Apply Summary +- Applied on branch `codex/pr-26171-031-idea-board-inline-tree-grid-actions`. +- Converted Idea Board into an inline table tree grid with selected notes expanded directly below the selected idea. +- Validation, ZIP, commit, PR, merge, and final main fields are recorded in `docs_build/dev/reports/codex_changed_files.txt`. + +## Requirement Evidence +- PASS evidence is recorded in `docs_build/dev/reports/codex_changed_files.txt`. +- The implementation is not docs-only; it changes Idea Board HTML, runtime JavaScript, and targeted Playwright behavior coverage. + +## Validation Evidence +- See `docs_build/dev/reports/codex_changed_files.txt`. +- See `docs_build/dev/reports/codex_review.diff`. diff --git a/docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md b/docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md new file mode 100644 index 000000000..6745640c8 --- /dev/null +++ b/docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md @@ -0,0 +1,48 @@ +# BUILD PR_26171_031-idea-board-inline-tree-grid-actions + +Source of truth: docs_build/dev/PROJECT_INSTRUCTIONS.md. + +## Exact Targets +- docs_build/dev/codex_commands.md +- docs_build/dev/commit_comment.txt +- docs_build/dev/reports/codex_changed_files.txt +- docs_build/dev/reports/codex_review.diff +- docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md +- docs_build/pr/BUILD_PR_26171_031-idea-board-inline-tree-grid-actions.md +- docs_build/pr/APPLY_PR_26171_031-idea-board-inline-tree-grid-actions.md +- tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +- tests/playwright/tools/ToolboxRoutePages.spec.mjs +- toolbox/idea-board/index.html +- toolbox/idea-board/index.js + +## Requirements +- Remove Owner column. +- Main table columns must be: Idea, Pitch, Status, Updated, Notes, Actions. +- Notes column must show note count text such as `2 Notes`. +- Selected idea must expand directly under its own idea row. +- Remove detached `Notes for Sky Orchard` section. +- Remove visible `Selected idea context` text. +- Remove visible notes metadata columns: Type, Created By, Created, Updated. +- Notes subtable visible columns must be: Note, Actions. +- Idea normal row actions must be Edit and Delete. +- Idea edit row actions must be Save and Cancel. +- Idea edit Status field must be a dropdown. +- Note normal row must expose Edit and Delete when allowed. +- System note must expose Edit and must not expose Delete. +- Note edit row actions must be Save and Cancel. +- Add Idea must open an inline idea row with Save and Cancel. +- Add Note must open an inline note row under the selected idea with Save and Cancel. +- Do not create docs-only work. +- Do not create detached panels. +- Do not use form-first UI. +- Produce repo-structured delta ZIP at `tmp/PR_26171_031-idea-board-inline-tree-grid-actions_delta.zip`. + +## Validation Commands +- `node --check toolbox/idea-board/index.js` +- `node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs` +- `node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs` +- `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line` +- `npm run test:workspace-v2` + +## ZIP Output +- `tmp/PR_26171_031-idea-board-inline-tree-grid-actions_delta.zip` diff --git a/docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md b/docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md new file mode 100644 index 000000000..c36d6eaba --- /dev/null +++ b/docs_build/pr/PLAN_PR_26171_031-idea-board-inline-tree-grid-actions.md @@ -0,0 +1,29 @@ +# PLAN PR_26171_031-idea-board-inline-tree-grid-actions + +Source of truth: docs_build/dev/PROJECT_INSTRUCTIONS.md. + +## Purpose +Fix Idea Board so the work surface is a table-first inline tree grid where the selected idea expands directly beneath its own row and all idea/note actions happen inline. + +## Scope +- Remove the detached selected notes section and selected idea context text. +- Remove the Owner column from the main idea table. +- Render main columns: Idea, Pitch, Status, Updated, Notes, Actions. +- Render selected idea notes as a subtable directly under the selected idea row. +- Hide note metadata columns from the notes subtable. +- Support inline Add/Edit/Delete actions for ideas and notes. +- Keep system note deletion disallowed while allowing system note editing. +- Update targeted Idea Board Playwright coverage, required reports, and ZIP output. + +## Validation +- `node --check toolbox/idea-board/index.js` +- `node --check tests/playwright/tools/IdeaBoardTableNotes.spec.mjs` +- `node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs` +- `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs --project=playwright --workers=1 --reporter=line` +- `npm run test:workspace-v2` + +## Git Workflow +- Start from refreshed `main`. +- Create branch `codex/pr-26171-031-idea-board-inline-tree-grid-actions`. +- Stage only scoped files. +- Commit, push, create PR, merge after validation, return to `main`, and pull latest `main`. diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs index d6f3668d0..c0d6ded06 100644 --- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs @@ -10,7 +10,7 @@ function restoreEnvValue(key, value) { process.env[key] = value; } -test("Idea Board presents a table-first work surface and governed notes workflow", async ({ page }) => { +test("Idea Board presents inline tree grid idea and note actions", async ({ page }) => { const server = await startRepoServer(); const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; const previousSiteUrl = process.env.GAMEFOUNDRY_SITE_URL; @@ -41,49 +41,83 @@ test("Idea Board presents a table-first work surface and governed notes workflow try { await page.goto(`${server.baseUrl}/toolbox/idea-board/index.html`, { waitUntil: "networkidle" }); await expect(page.getByRole("heading", { level: 1, name: "Idea Board" })).toBeVisible(); - await expect(page.locator("[data-idea-board-table] th[scope='col']")).toHaveText(["Idea", "Pitch", "Status", "Owner", "Updated", "Notes"]); - await expect(page.locator("[data-idea-board-table] tbody tr")).toHaveCount(2); + await expect(page.locator("[data-idea-board-table] > thead th[scope='col']")).toHaveText([ + "Idea", + "Pitch", + "Status", + "Updated", + "Notes", + "Actions", + ]); + await expect(page.locator("[data-idea-board-table] > thead th[scope='col']", { hasText: "Owner" })).toHaveCount(0); + await expect(page.locator("[data-idea-board-idea-row]")).toHaveCount(2); + await expect(page.locator("[data-idea-board-section='Selected Notes']")).toHaveCount(0); + await expect(page.getByText("Notes for Sky Orchard")).toHaveCount(0); + await expect(page.getByText("Selected idea context")).toHaveCount(0); await expect(page.locator("[data-idea-board-idea-row='sky-orchard']")).toHaveAttribute("aria-selected", "true"); - await expect(page.locator("[data-idea-board-selected-title]")).toHaveText("Notes for Sky Orchard"); - await expect(page.locator("[data-idea-board-notes-table] th[scope='col']")).toHaveText(["Note", "Type", "Created By", "Created", "Updated", "Actions"]); - await expect(page.locator("[data-idea-board-system-note] [data-idea-board-action='delete']")).toHaveCount(0); - await expect(page.locator("[data-idea-board-system-note] [data-idea-board-action='edit']")).toHaveCount(0); - await expect(page.locator("[data-idea-board-system-note]").first()).toContainText("System locked"); - await page.locator("[data-idea-board-add-note]").click(); - await expect(page.locator("[data-idea-board-inline-input-row]")).toHaveCount(1); - await expect(page.locator("[data-idea-board-inline-input-row] [data-idea-board-action]")).toHaveText(["Save", "Cancel"]); - await page.locator("[data-idea-board-note-input]").fill("Prototype the storm creature escalation table."); - await page.locator("[data-idea-board-action='save']").click(); - const skyCreatorNote = page.locator("[data-idea-board-notes-table] tbody tr").first(); - await expect(skyCreatorNote).toContainText("Prototype the storm creature escalation table."); - await expect(skyCreatorNote.locator("td").nth(1)).toHaveText("Creator"); - await expect(skyCreatorNote.locator("td").nth(2)).toHaveText("Creator"); + const initialRowOrder = await page.locator("[data-idea-board-ideas-body] > tr").evaluateAll((rows) => ( + rows.map((row) => row.getAttribute("data-idea-board-idea-row") || row.getAttribute("data-idea-board-expanded-row")) + )); + expect(initialRowOrder.slice(0, 3)).toEqual(["sky-orchard", "sky-orchard", "clockwork-courier"]); + await expect(page.locator("[data-idea-board-idea-row='sky-orchard'] [data-idea-board-select-idea]")).toHaveText("2 Notes"); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard'] th[scope='col']")).toHaveText(["Note", "Actions"]); + await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Type" })).toHaveCount(0); + await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Created By" })).toHaveCount(0); + await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Created" })).toHaveCount(0); + await expect(page.locator("[data-idea-board-notes-table] th[scope='col']", { hasText: "Updated" })).toHaveCount(0); + await expect(page.locator("[data-idea-board-idea-row='sky-orchard'] [data-idea-board-idea-action]")).toHaveText(["Edit", "Delete"]); - await page.locator("[data-idea-board-select-idea='clockwork-courier']").click(); - await expect(page.locator("[data-idea-board-idea-row='clockwork-courier']")).toHaveAttribute("aria-selected", "true"); - await expect(page.locator("[data-idea-board-selected-title]")).toHaveText("Notes for Clockwork Courier"); - await expect(page.locator("[data-idea-board-notes-table]")).not.toContainText("Prototype the storm creature escalation table."); - await expect(page.locator("[data-idea-board-notes-table]")).toContainText("Check whether district routing stays readable after the first reset."); + const systemNote = page.locator("[data-idea-board-system-note]").first(); + await expect(systemNote.locator("[data-idea-board-note-action]")).toHaveText(["Edit"]); + await expect(systemNote.locator("[data-idea-board-note-action='delete']")).toHaveCount(0); + await systemNote.locator("[data-idea-board-note-action='edit']").click(); + await expect(page.locator("[data-idea-board-note-input-row] [data-idea-board-note-action]")).toHaveText(["Save", "Cancel"]); + await page.locator("[data-idea-board-note-input]").fill("System note edited inline for Sky Orchard."); + await page.locator("[data-idea-board-note-action='save']").click(); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard']")).toContainText("System note edited inline for Sky Orchard."); + await expect(page.locator("[data-idea-board-system-note] [data-idea-board-note-action='delete']")).toHaveCount(0); - await page.locator("[data-idea-board-add-note]").click(); - await page.locator("[data-idea-board-note-input]").fill("Map the reset rules before writing project records."); - await page.locator("[data-idea-board-action='save']").click(); - await expect(page.locator("[data-idea-board-notes-table]")).toContainText("Map the reset rules before writing project records."); + await page.locator("[data-idea-board-add-note='sky-orchard']").click(); + await expect(page.locator("[data-idea-board-note-input-row] [data-idea-board-note-action]")).toHaveText(["Save", "Cancel"]); + await page.locator("[data-idea-board-note-input]").fill("Prototype the storm creature escalation table."); + await page.locator("[data-idea-board-note-action='save']").click(); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard']")).toContainText("Prototype the storm creature escalation table."); + await expect(page.locator("[data-idea-board-idea-row='sky-orchard'] [data-idea-board-select-idea]")).toHaveText("3 Notes"); - await page.locator("[data-idea-board-select-idea='sky-orchard']").click(); - await expect(page.locator("[data-idea-board-selected-title]")).toHaveText("Notes for Sky Orchard"); - await expect(page.locator("[data-idea-board-notes-table]")).toContainText("Prototype the storm creature escalation table."); - await expect(page.locator("[data-idea-board-notes-table]")).not.toContainText("Map the reset rules before writing project records."); - await page.locator("[data-idea-board-notes-table] tbody tr").first().locator("[data-idea-board-action='edit']").click(); - await page.locator("[data-idea-board-note-input]").fill("Prototype storm creature escalation after core loop review."); - await page.locator("[data-idea-board-action='save']").click(); - await expect(page.locator("[data-idea-board-notes-table]")).toContainText("Prototype storm creature escalation after core loop review."); + await page.locator("[data-idea-board-notes-table='sky-orchard'] tbody tr").first().locator("[data-idea-board-note-action='edit']").click(); + await page.locator("[data-idea-board-note-input]").fill("Prototype storm escalation after core loop review."); + await page.locator("[data-idea-board-note-action='save']").click(); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard']")).toContainText("Prototype storm escalation after core loop review."); + await page.locator("[data-idea-board-notes-table='sky-orchard'] tbody tr").first().locator("[data-idea-board-note-action='delete']").click(); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard']")).not.toContainText("Prototype storm escalation after core loop review."); - await page.locator("[data-idea-board-notes-table] tbody tr").first().locator("[data-idea-board-action='delete']").click(); - await expect(page.locator("[data-idea-board-notes-table]")).not.toContainText("Prototype storm creature escalation after core loop review."); await page.locator("[data-idea-board-select-idea='clockwork-courier']").click(); - await expect(page.locator("[data-idea-board-notes-table]")).toContainText("Map the reset rules before writing project records."); + const clockRowOrder = await page.locator("[data-idea-board-ideas-body] > tr").evaluateAll((rows) => ( + rows.map((row) => row.getAttribute("data-idea-board-idea-row") || row.getAttribute("data-idea-board-expanded-row")) + )); + expect(clockRowOrder.slice(0, 3)).toEqual(["sky-orchard", "clockwork-courier", "clockwork-courier"]); + await expect(page.locator("[data-idea-board-notes-table='clockwork-courier']")).toContainText("Check whether district routing stays readable after the first reset."); + await expect(page.locator("[data-idea-board-notes-table='clockwork-courier']")).not.toContainText("System note edited inline for Sky Orchard."); + + await page.locator("[data-idea-board-add-idea]").click(); + await expect(page.locator("[data-idea-board-idea-input-row] [data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); + await expect(page.locator("[data-idea-board-idea-input-row] [data-idea-board-idea-status-input]")).toHaveCount(1); + await page.locator("[data-idea-board-idea-title-input]").fill("Lantern Reef"); + await page.locator("[data-idea-board-idea-pitch-input]").fill("Guide light through a reef that rearranges at dusk."); + await page.locator("[data-idea-board-idea-status-input]").selectOption("Parked"); + await page.locator("[data-idea-board-idea-action='save']").click(); + await expect(page.locator("[data-idea-board-idea-row='lantern-reef']")).toBeVisible(); + await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-select-idea]")).toHaveText("0 Notes"); + await expect(page.locator("[data-idea-board-expanded-row='lantern-reef']")).toBeVisible(); + + await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='edit']").click(); + await expect(page.locator("[data-idea-board-idea-input-row] [data-idea-board-idea-action]")).toHaveText(["Save", "Cancel"]); + await page.locator("[data-idea-board-idea-status-input]").selectOption("Ready to Shape"); + await page.locator("[data-idea-board-idea-action='save']").click(); + await expect(page.locator("[data-idea-board-idea-row='lantern-reef'] td").nth(1)).toHaveText("Ready to Shape"); + await page.locator("[data-idea-board-idea-row='lantern-reef'] [data-idea-board-idea-action='delete']").click(); + await expect(page.locator("[data-idea-board-idea-row='lantern-reef']")).toHaveCount(0); expect(mutatingApiRequests).toEqual([]); expect(failedRequests).toEqual([]); diff --git a/tests/playwright/tools/ToolboxRoutePages.spec.mjs b/tests/playwright/tools/ToolboxRoutePages.spec.mjs index d80985c11..d7bdf8c98 100644 --- a/tests/playwright/tools/ToolboxRoutePages.spec.mjs +++ b/tests/playwright/tools/ToolboxRoutePages.spec.mjs @@ -155,7 +155,7 @@ test("tools route aliases render toolbox tool pages", async ({ page }) => { } }); -test("Idea Board launches from Toolbox with table-first selected notes context", async ({ page }) => { +test("Idea Board launches from Toolbox with inline tree grid notes context", async ({ page }) => { const server = await startRepoServer(); const previousApiUrl = process.env.GAMEFOUNDRY_API_URL; const previousSiteUrl = process.env.GAMEFOUNDRY_SITE_URL; @@ -215,29 +215,32 @@ test("Idea Board launches from Toolbox with table-first selected notes context", "Workflow", "Status", "Idea Table", - "Selected Notes", "Create Project", "Notes Governance", "Diagnostics", ]); await expect(page.locator("[data-idea-board-table]")).toBeVisible(); - await expect(page.locator("[data-idea-board-notes-table]")).toBeVisible(); - await expect(page.locator("[data-idea-board-selected-title]")).toHaveText("Notes for Sky Orchard"); - await expect(page.locator("[data-idea-board-add-note]")).toBeVisible(); + await expect(page.locator("[data-idea-board-table] > thead th[scope='col']")).toHaveText(["Idea", "Pitch", "Status", "Updated", "Notes", "Actions"]); + await expect(page.locator("[data-idea-board-expanded-row='sky-orchard']")).toBeVisible(); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard']")).toBeVisible(); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard'] th[scope='col']")).toHaveText(["Note", "Actions"]); + await expect(page.getByText("Notes for Sky Orchard")).toHaveCount(0); + await expect(page.getByText("Selected idea context")).toHaveCount(0); + await expect(page.locator("[data-idea-board-add-note='sky-orchard']")).toBeVisible(); await expect(page.locator("[data-idea-board-create-project]")).toBeVisible(); await expect(page.locator("[data-idea-board-create-project]")).toBeDisabled(); - await expect(page.locator("[data-idea-board-status]")).toHaveText("Idea Board table is active in-page only. No save, load, auth, AI, or database behavior is connected."); + await expect(page.locator("[data-idea-board-status]")).toHaveText("Idea Board table edits are in-page only. No project records, auth, AI, or database behavior is connected."); await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0); await expect(page.locator("script[src='toolbox/idea-board/index.js']")).toHaveCount(1); mutatingApiRequests.length = 0; await page.locator("[data-idea-board-create-project]").evaluate((button) => button.click()); - await page.locator("[data-idea-board-add-note]").click(); + await page.locator("[data-idea-board-add-note='sky-orchard']").click(); await page.locator("[data-idea-board-note-input]").fill("Capture traversal risks before project creation."); - await page.locator("[data-idea-board-action='save']").click(); - await expect(page.locator("[data-idea-board-notes-table]")).toContainText("Capture traversal risks before project creation."); + await page.locator("[data-idea-board-note-action='save']").click(); + await expect(page.locator("[data-idea-board-notes-table='sky-orchard']")).toContainText("Capture traversal risks before project creation."); await page.locator("[data-idea-board-select-idea='clockwork-courier']").click(); - await expect(page.locator("[data-idea-board-selected-title]")).toHaveText("Notes for Clockwork Courier"); - await expect(page.locator("[data-idea-board-notes-table]")).not.toContainText("Capture traversal risks before project creation."); + await expect(page.locator("[data-idea-board-expanded-row='clockwork-courier']")).toBeVisible(); + await expect(page.locator("[data-idea-board-notes-table='clockwork-courier']")).not.toContainText("Capture traversal risks before project creation."); expect(mutatingApiRequests).toEqual([]); expect(failedRequests).toEqual([]); diff --git a/toolbox/idea-board/index.html b/toolbox/idea-board/index.html index 4ae004668..0b38edaaf 100644 --- a/toolbox/idea-board/index.html +++ b/toolbox/idea-board/index.html @@ -49,63 +49,22 @@

Idea Work Surface

The primary surface is a table so creators can scan, compare, and govern early ideas without a form-first workflow.

- + - + - - - - - - - - - - - - - - - - - - +
Idea Board table-first work surfaceIdea Board inline tree grid work surface
Idea Pitch StatusOwner Updated NotesActions
Sky OrchardGrow floating islands while defending them from storm creatures.ExploringCreator2026-06-20 - -
Clockwork CourierDeliver messages through looping city districts before time resets.NewCreator2026-06-20 - -
-
-

Notes for Sky Orchard

-

Selected idea context: Exploring, owned by Creator, updated 2026-06-20.

-
- - - - - - - - - - - - - -
Selected idea notes for Sky Orchard
NoteTypeCreated ByCreatedUpdatedActions
-
-
- -
+
+
diff --git a/toolbox/idea-board/index.js b/toolbox/idea-board/index.js index 59424bf5b..711506b77 100644 --- a/toolbox/idea-board/index.js +++ b/toolbox/idea-board/index.js @@ -1,10 +1,11 @@ -const ideas = Object.freeze([ +const statusOptions = Object.freeze(["New", "Exploring", "Parked", "Ready to Shape"]); + +const ideas = [ { id: "sky-orchard", title: "Sky Orchard", pitch: "Grow floating islands while defending them from storm creatures.", status: "Exploring", - owner: "Creator", updated: "2026-06-20", }, { @@ -12,10 +13,9 @@ const ideas = Object.freeze([ title: "Clockwork Courier", pitch: "Deliver messages through looping city districts before time resets.", status: "New", - owner: "Creator", updated: "2026-06-20", }, -]); +]; const notesByIdea = new Map([ [ @@ -24,18 +24,12 @@ const notesByIdea = new Map([ { id: "sky-system-origin", note: "System seed note: compare early Sky Orchard ideas before creating a project.", - type: "System", - createdBy: "System", - created: "2026-06-20", updated: "2026-06-20", system: true, }, { id: "sky-creator-next-question", note: "Ask whether the core loop is resource planning, action defense, or both.", - type: "Creator", - createdBy: "Creator", - created: "2026-06-20", updated: "2026-06-20", system: false, }, @@ -47,18 +41,12 @@ const notesByIdea = new Map([ { id: "clock-system-origin", note: "System seed note: keep Clockwork Courier scoped until the time-loop hook is clear.", - type: "System", - createdBy: "System", - created: "2026-06-20", updated: "2026-06-20", system: true, }, { id: "clock-creator-route-risk", note: "Check whether district routing stays readable after the first reset.", - type: "Creator", - createdBy: "Creator", - created: "2026-06-20", updated: "2026-06-20", system: false, }, @@ -68,7 +56,9 @@ const notesByIdea = new Map([ const state = { selectedIdeaId: "sky-orchard", + editingIdeaId: null, editingNoteId: null, + addingIdea: false, addingNote: false, }; @@ -77,11 +67,17 @@ function today() { } function selectedIdea() { - return ideas.find((idea) => idea.id === state.selectedIdeaId); + return ideas.find((idea) => idea.id === state.selectedIdeaId) || null; +} + +function notesForIdea(ideaId) { + if (!notesByIdea.has(ideaId)) notesByIdea.set(ideaId, []); + return notesByIdea.get(ideaId); } -function selectedNotes() { - return notesByIdea.get(state.selectedIdeaId); +function noteCountLabel(ideaId) { + const count = notesForIdea(ideaId).length; + return `${count} ${count === 1 ? "Note" : "Notes"}`; } function cell(text) { @@ -90,70 +86,128 @@ function cell(text) { return td; } -function actionButton(label, action, variant = "") { +function actionButton(label, action, datasetName, variant = "") { const control = document.createElement("button"); control.className = variant ? `btn btn--compact ${variant}` : "btn btn--compact"; control.type = "button"; control.textContent = label; - control.dataset.ideaBoardAction = action; + control.dataset[datasetName] = action; return control; } +function textInput(label, value = "") { + const input = document.createElement("input"); + input.type = "text"; + input.value = value; + input.setAttribute("aria-label", label); + return input; +} + +function statusSelect(value) { + const select = document.createElement("select"); + select.setAttribute("aria-label", "Idea status"); + select.dataset.ideaBoardIdeaStatusInput = "true"; + for (const optionValue of statusOptions) { + const option = document.createElement("option"); + option.value = optionValue; + option.textContent = optionValue; + option.selected = optionValue === value; + select.append(option); + } + return select; +} + function updateStatus(root, message) { const status = root.querySelector("[data-idea-board-status]"); if (status) status.textContent = message; } -function renderIdeaSelection(root) { - for (const row of root.querySelectorAll("[data-idea-board-idea-row]")) { - const rowSelected = row.dataset.ideaBoardIdeaRow === state.selectedIdeaId; - row.setAttribute("aria-selected", String(rowSelected)); - const selectButton = row.querySelector("[data-idea-board-select-idea]"); - if (selectButton) { - selectButton.disabled = rowSelected; - selectButton.classList.toggle("primary", rowSelected); - selectButton.textContent = rowSelected ? "Selected" : "Select"; - } - } +function renderIdeaInputRow(tbody, idea = null) { + const row = document.createElement("tr"); + row.dataset.ideaBoardIdeaInputRow = "true"; + if (idea) row.dataset.ideaId = idea.id; + + const titleCell = document.createElement("th"); + titleCell.scope = "row"; + const titleInput = textInput(idea ? "Edit idea title" : "New idea title", idea?.title || ""); + titleInput.dataset.ideaBoardIdeaTitleInput = "true"; + titleCell.append(titleInput); + row.append(titleCell); + + const pitchCell = document.createElement("td"); + const pitchInput = textInput(idea ? "Edit idea pitch" : "New idea pitch", idea?.pitch || ""); + pitchInput.dataset.ideaBoardIdeaPitchInput = "true"; + pitchCell.append(pitchInput); + row.append(pitchCell); + + const statusCell = document.createElement("td"); + statusCell.append(statusSelect(idea?.status || "New")); + row.append(statusCell); + row.append(cell(idea?.updated || today())); + row.append(cell(idea ? noteCountLabel(idea.id) : "0 Notes")); + + const actions = document.createElement("td"); + actions.append( + actionButton("Save", "save", "ideaBoardIdeaAction", "primary"), + " ", + actionButton("Cancel", "cancel", "ideaBoardIdeaAction"), + ); + row.append(actions); + tbody.append(row); + titleInput.focus(); } -function renderSelectedIdeaContext(root) { - const idea = selectedIdea(); - if (!idea) { - updateStatus(root, `Idea Board cannot find selected idea: ${state.selectedIdeaId}.`); - return; - } - const title = root.querySelector("[data-idea-board-selected-title]"); - const summary = root.querySelector("[data-idea-board-selected-summary]"); - const caption = root.querySelector("[data-idea-board-notes-caption]"); - if (title) title.textContent = `Notes for ${idea.title}`; - if (summary) { - summary.textContent = `Selected idea context: ${idea.status}, owned by ${idea.owner}, updated ${idea.updated}.`; - } - if (caption) caption.textContent = `Selected idea notes for ${idea.title}`; +function renderIdeaRow(tbody, idea) { + const row = document.createElement("tr"); + const selected = idea.id === state.selectedIdeaId; + row.dataset.ideaBoardIdeaRow = idea.id; + row.setAttribute("aria-selected", String(selected)); + + const title = document.createElement("th"); + title.scope = "row"; + title.textContent = idea.title; + row.append(title); + row.append(cell(idea.pitch)); + row.append(cell(idea.status)); + row.append(cell(idea.updated)); + + const notes = document.createElement("td"); + const notesButton = document.createElement("button"); + notesButton.className = selected ? "btn btn--compact primary" : "btn btn--compact"; + notesButton.type = "button"; + notesButton.textContent = noteCountLabel(idea.id); + notesButton.dataset.ideaBoardSelectIdea = idea.id; + notesButton.setAttribute("aria-expanded", String(selected)); + notes.append(notesButton); + row.append(notes); + + const actions = document.createElement("td"); + actions.append( + actionButton("Edit", "edit", "ideaBoardIdeaAction"), + " ", + actionButton("Delete", "delete", "ideaBoardIdeaAction"), + ); + row.append(actions); + tbody.append(row); } -function renderInputRow(tbody, note = null) { +function renderNoteInputRow(tbody, note = null) { const row = document.createElement("tr"); - row.dataset.ideaBoardInlineInputRow = "true"; + row.dataset.ideaBoardNoteInputRow = "true"; if (note) row.dataset.noteId = note.id; const noteCell = document.createElement("td"); - const input = document.createElement("input"); - input.type = "text"; - input.value = note?.note || ""; - input.setAttribute("aria-label", note ? "Edit selected idea note text" : "New selected idea note text"); + const input = textInput(note ? "Edit note text" : "New note text", note?.note || ""); input.dataset.ideaBoardNoteInput = "true"; noteCell.append(input); row.append(noteCell); - row.append(cell(note?.type || "Creator")); - row.append(cell(note?.createdBy || "Creator")); - row.append(cell(note?.created || today())); - row.append(cell(today())); - const actions = document.createElement("td"); - actions.append(actionButton("Save", "save", "primary"), " ", actionButton("Cancel", "cancel")); + actions.append( + actionButton("Save", "save", "ideaBoardNoteAction", "primary"), + " ", + actionButton("Cancel", "cancel", "ideaBoardNoteAction"), + ); row.append(actions); tbody.append(row); input.focus(); @@ -164,46 +218,116 @@ function renderNoteRow(tbody, note) { row.dataset.noteId = note.id; if (note.system) row.dataset.ideaBoardSystemNote = "true"; row.append(cell(note.note)); - row.append(cell(note.type)); - row.append(cell(note.createdBy)); - row.append(cell(note.created)); - row.append(cell(note.updated)); const actions = document.createElement("td"); - if (note.system) { - actions.textContent = "System locked"; - } else { - actions.append(actionButton("Edit", "edit"), " ", actionButton("Delete", "delete")); + actions.append(actionButton("Edit", "edit", "ideaBoardNoteAction")); + if (!note.system) { + actions.append(" ", actionButton("Delete", "delete", "ideaBoardNoteAction")); } row.append(actions); tbody.append(row); } -function renderNotes(root) { - const tbody = root.querySelector("[data-idea-board-notes-body]"); - const notes = selectedNotes(); - if (!tbody || !notes) { - updateStatus(root, `Idea Board cannot load notes for selected idea: ${state.selectedIdeaId}.`); - return; - } - tbody.replaceChildren(); - if (state.addingNote) renderInputRow(tbody); - for (const note of notes) { +function renderExpandedNotesRow(tbody, idea) { + const row = document.createElement("tr"); + row.dataset.ideaBoardExpandedRow = idea.id; + + const content = document.createElement("td"); + content.colSpan = 6; + + const wrapper = document.createElement("div"); + wrapper.className = "content-stack"; + + const tableWrapper = document.createElement("div"); + tableWrapper.className = "table-wrapper"; + const notesTable = document.createElement("table"); + notesTable.className = "data-table data-table--fixed"; + notesTable.dataset.ideaBoardNotesTable = idea.id; + notesTable.setAttribute("aria-label", `${idea.title} notes`); + notesTable.innerHTML = "NoteActions"; + + const notesBody = document.createElement("tbody"); + notesBody.dataset.ideaBoardNotesBody = idea.id; + notesTable.append(notesBody); + tableWrapper.append(notesTable); + wrapper.append(tableWrapper); + + const controls = document.createElement("div"); + controls.className = "action-group"; + const addNote = actionButton("Add Note", "add", "ideaBoardNoteAction", "primary"); + addNote.dataset.ideaBoardAddNote = idea.id; + controls.append(addNote); + wrapper.append(controls); + + content.append(wrapper); + row.append(content); + tbody.append(row); + + if (state.addingNote) renderNoteInputRow(notesBody); + for (const note of notesForIdea(idea.id)) { if (state.editingNoteId === note.id) { - renderInputRow(tbody, note); + renderNoteInputRow(notesBody, note); } else { - renderNoteRow(tbody, note); + renderNoteRow(notesBody, note); } } } function render(root) { - renderIdeaSelection(root); - renderSelectedIdeaContext(root); - renderNotes(root); + const tbody = root.querySelector("[data-idea-board-ideas-body]"); + if (!tbody) return; + tbody.replaceChildren(); + for (const idea of ideas) { + if (state.editingIdeaId === idea.id) { + renderIdeaInputRow(tbody, idea); + } else { + renderIdeaRow(tbody, idea); + } + if (state.selectedIdeaId === idea.id) renderExpandedNotesRow(tbody, idea); + } + if (state.addingIdea) renderIdeaInputRow(tbody); +} + +function slugifyTitle(title) { + const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""); + return slug || `idea-${Date.now()}`; } -function saveRow(root, row) { +function saveIdeaRow(root, row) { + const title = row.querySelector("[data-idea-board-idea-title-input]")?.value.trim(); + const pitch = row.querySelector("[data-idea-board-idea-pitch-input]")?.value.trim(); + const status = row.querySelector("[data-idea-board-idea-status-input]")?.value; + if (!title || !pitch || !status) { + updateStatus(root, "Enter an idea, pitch, and status before saving."); + return; + } + + const ideaId = row.dataset.ideaId; + if (ideaId) { + const idea = ideas.find((item) => item.id === ideaId); + if (!idea) { + updateStatus(root, `Idea Board cannot find idea: ${ideaId}.`); + return; + } + idea.title = title; + idea.pitch = pitch; + idea.status = status; + idea.updated = today(); + state.editingIdeaId = null; + updateStatus(root, `Updated ${idea.title}.`); + } else { + const baseId = slugifyTitle(title); + const id = ideas.some((idea) => idea.id === baseId) ? `${baseId}-${Date.now()}` : baseId; + ideas.push({ id, title, pitch, status, updated: today() }); + notesByIdea.set(id, []); + state.selectedIdeaId = id; + state.addingIdea = false; + updateStatus(root, `Added ${title}.`); + } + render(root); +} + +function saveNoteRow(root, row) { const input = row.querySelector("[data-idea-board-note-input]"); const value = input?.value.trim(); if (!value) { @@ -211,17 +335,12 @@ function saveRow(root, row) { return; } - const notes = selectedNotes(); - if (!notes) { - updateStatus(root, `Idea Board cannot save notes for selected idea: ${state.selectedIdeaId}.`); - return; - } - + const notes = notesForIdea(state.selectedIdeaId); const noteId = row.dataset.noteId; if (noteId) { - const note = notes.find((item) => item.id === noteId && !item.system); + const note = notes.find((item) => item.id === noteId); if (!note) { - updateStatus(root, "Only creator notes can be edited."); + updateStatus(root, `Idea Board cannot find note: ${noteId}.`); return; } note.note = value; @@ -230,11 +349,8 @@ function saveRow(root, row) { updateStatus(root, `Updated note for ${selectedIdea().title}.`); } else { notes.unshift({ - id: `creator-${state.selectedIdeaId}-${Date.now()}`, + id: `note-${state.selectedIdeaId}-${Date.now()}`, note: value, - type: "Creator", - createdBy: "Creator", - created: today(), updated: today(), system: false, }); @@ -244,51 +360,117 @@ function saveRow(root, row) { render(root); } -function handleClick(root, event) { - const ideaButton = event.target.closest("[data-idea-board-select-idea]"); - if (ideaButton) { - state.selectedIdeaId = ideaButton.dataset.ideaBoardSelectIdea; - state.addingNote = false; - state.editingNoteId = null; - updateStatus(root, `Selected ${selectedIdea().title}. Notes now show that idea's context.`); - render(root); +function selectIdea(root, ideaId) { + if (!ideas.some((idea) => idea.id === ideaId)) { + updateStatus(root, `Idea Board cannot select missing idea: ${ideaId}.`); return; } + state.selectedIdeaId = ideaId; + state.editingIdeaId = null; + state.editingNoteId = null; + state.addingNote = false; + updateStatus(root, `Expanded notes for ${selectedIdea().title}.`); + render(root); +} - const addButton = event.target.closest("[data-idea-board-add-note]"); - if (addButton) { - state.addingNote = true; +function deleteIdea(root, ideaId) { + const index = ideas.findIndex((idea) => idea.id === ideaId); + if (index < 0) { + updateStatus(root, `Idea Board cannot delete missing idea: ${ideaId}.`); + return; + } + const [removed] = ideas.splice(index, 1); + notesByIdea.delete(ideaId); + if (state.selectedIdeaId === ideaId) { + state.selectedIdeaId = ideas[Math.min(index, ideas.length - 1)]?.id || ""; state.editingNoteId = null; - updateStatus(root, `Adding a note for ${selectedIdea().title}.`); + state.addingNote = false; + } + updateStatus(root, `Deleted ${removed.title}.`); + render(root); +} + +function handleIdeaAction(root, actionControl) { + const action = actionControl.dataset.ideaBoardIdeaAction; + const row = actionControl.closest("tr"); + const ideaId = actionControl.dataset.ideaBoardSelectIdea || row?.dataset.ideaBoardIdeaRow || row?.dataset.ideaId; + if (action === "select") { + selectIdea(root, ideaId); + } else if (action === "edit") { + state.editingIdeaId = ideaId; + state.addingIdea = false; + state.editingNoteId = null; + state.addingNote = false; + updateStatus(root, `Editing ${ideas.find((idea) => idea.id === ideaId)?.title}.`); render(root); - return; + } else if (action === "delete") { + deleteIdea(root, ideaId); + } else if (action === "cancel") { + state.editingIdeaId = null; + state.addingIdea = false; + updateStatus(root, "Cancelled idea edit."); + render(root); + } else if (action === "save") { + saveIdeaRow(root, row); } +} - const actionControl = event.target.closest("[data-idea-board-action]"); - if (!actionControl) return; +function handleNoteAction(root, actionControl) { + const action = actionControl.dataset.ideaBoardNoteAction; const row = actionControl.closest("tr"); const noteId = row?.dataset.noteId; - const action = actionControl.dataset.ideaBoardAction; - if (action === "edit") { + if (action === "add") { + state.addingNote = true; + state.editingNoteId = null; + updateStatus(root, `Adding a note for ${selectedIdea().title}.`); + render(root); + } else if (action === "edit") { state.editingNoteId = noteId; state.addingNote = false; updateStatus(root, `Editing note for ${selectedIdea().title}.`); + render(root); } else if (action === "delete") { - const notes = selectedNotes(); - const index = notes?.findIndex((note) => note.id === noteId && !note.system) ?? -1; + const notes = notesForIdea(state.selectedIdeaId); + const index = notes.findIndex((note) => note.id === noteId && !note.system); if (index >= 0) { notes.splice(index, 1); updateStatus(root, `Deleted note for ${selectedIdea().title}.`); + render(root); } } else if (action === "cancel") { state.editingNoteId = null; state.addingNote = false; updateStatus(root, `Cancelled note edit for ${selectedIdea().title}.`); + render(root); } else if (action === "save") { - saveRow(root, row); + saveNoteRow(root, row); + } +} + +function handleClick(root, event) { + const addIdea = event.target.closest("[data-idea-board-add-idea]"); + if (addIdea) { + state.addingIdea = true; + state.editingIdeaId = null; + updateStatus(root, "Adding a new idea."); + render(root); return; } - render(root); + + const selectIdeaButton = event.target.closest("[data-idea-board-select-idea]"); + if (selectIdeaButton) { + selectIdea(root, selectIdeaButton.dataset.ideaBoardSelectIdea); + return; + } + + const ideaAction = event.target.closest("[data-idea-board-idea-action]"); + if (ideaAction) { + handleIdeaAction(root, ideaAction); + return; + } + + const noteAction = event.target.closest("[data-idea-board-note-action]"); + if (noteAction) handleNoteAction(root, noteAction); } document.addEventListener("DOMContentLoaded", () => {