IM-4b: Wall collaboration + multi-scope rail + re-mounted detached flows#257
Merged
Conversation
…ptance Adds RED tests for: - editHubComment / deleteHubComment store actions (7 tests in analyzeStore.test.ts) - HypothesisComments component: thread rendering, ACL-gated add/edit/delete composer (14 tests in HypothesisComments.test.tsx) All 97 pre-existing analyzeStore tests pass; FindingComments tests pass. New tests fail for the right reason (actions not a function / module not found). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lift/parameterize FindingComments to support ACL-gated write access: - Add canEdit?: boolean prop (defaults true) to FindingComments; gates add/edit/delete affordances so read-only viewers see no write surface. - Add HypothesisComments component (AnalyzeWall) that wraps FindingComments, binds hub.comments as the data source, and computes canEdit from canAccess(currentUserId, members, 'edit-contributions') — mirrors the HypothesisCardWithPlans ACL gate pattern. - Add editHubComment / deleteHubComment store actions to analyzeStore (twins of editFindingComment / deleteFindingComment, keyed to hypotheses). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ts for acceptance - New: packages/core/src/findings/__tests__/parseMentions.test.ts — 10 tests for the parseMentions(text, members) pure utility (ERR_MODULE_NOT_FOUND, RED) - Augment: buildAIContext.test.ts — 5 new failing tests for investigation.recentComments (hubComments/findingComments/todayStartMs options not yet on BuildAIContextOptions; 2 negative-case tests correctly pass) - Augment: analyzeStore.test.ts — 4 tests for addHubComment @mention storage (mentionedUserIds arg not yet accepted; 3 fail, 1 backward-compat passes) All 67+105+18 pre-existing tests in the touched files remain green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- parseMentions(text, members): new pure utility in findings barrel that resolves @DisplayName tokens to userId strings (case-insensitive, deduplicated, unknown tags ignored). - FindingComment.mentionedUserIds?: string[] — optional field added for backward-compatible mention storage. - addHubComment: accepts optional mentionedUserIds arg; stores it on the comment + includes it in the POST body to /api/hub-comments/append. - BuildAIContextOptions: adds hubComments, findingComments, todayStartMs. - AIContext.investigation.recentComments: populated with text of today's hub + finding comments (createdAt >= todayStartMs), capped at 10, sorted newest-first; absent when no today-comments exist. Reuses: createFindingComment factory, BuildAIContextOptions pattern, analyzeStore addHubComment structure (Task 1 composer), projectMembership ProjectMember type. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… acceptance Encodes the acceptance oracle for Task 3 across three layers: - packages/core: hypothesisActionItems.test.ts — Hypothesis.actions field shape, new HubAction kinds (HYPOTHESIS_ACTION_ADD/UPDATE/COMPLETE) under the discriminated union, exhaustiveness mirror with the 3 new cases, and the MeasurementPlan-distinctness constraint (no primaryFactor/method on ActionItem). - packages/stores: analyzeStore.hypothesisActionItems.test.ts — 14 RED tests for addHypothesisAction / updateHypothesisAction / completeHypothesisAction / toggleHypothesisActionComplete / deleteHypothesisAction (all fail today with "is not a function"). - packages/ui: HypothesisCardWithPlans.actionItems.test.tsx — 12 RED / 4 GREEN UI tests: action item row rendering (text, assignee, open/done status, data-testid distinction from chip-body), + Add Task ACL gate (lead/sponsor/ empty-members visible; non-member hidden; no-callback omitted), add-task form flow (open/save/cancel), complete-task flow. All existing tests unbroken. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Reuses the existing ActionItem model (assignee/dueDate/completedAt) to attach tasks to Hypothesis hubs — mirroring the pattern already live on Finding.actions and canvas steps. Changes: - Hypothesis.actions?: ActionItem[] field added to core types - HYPOTHESIS_ACTION_ADD / UPDATE / COMPLETE added to HypothesisAction + HubAction discriminated union (kept exhaustive — exhaustiveness.test updated to cover new cases; hypothesisActionItems.test mirrors it) - analyzeStore: addHypothesisAction / updateHypothesisAction / completeHypothesisAction / toggleHypothesisActionComplete / deleteHypothesisAction — all wired to Hypothesis.actions[], reusing createActionItem from @variscout/core - HypothesisCardWithPlans: renders action-item-row divs (data-testid + data-status), "Mark Done" button (ACL-gated), "+ Add Task" button (omitted when callback not provided), inline add-task form with Save/ Cancel; onAddHypothesisAction + onCompleteHypothesisAction optional props - i18n: 5 wall.task.* keys added to types.ts + all 32 locale files Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tests for acceptance
Encodes the acceptance oracle for the plan-owner data-collection task surface
on HypothesisCardWithPlans: data-testid="data-collection-task" section per plan,
"Assigned: collect {primaryFactor}" label, owner display name resolved from roster,
per-status label + data-status attribute, optional dueDate display, and the section
being distinct from Task 3 action-item-row rows and not write-ACL-gated (read display).
All 21 new tests fail RED (data-testid="data-collection-task" is absent from current DOM);
1 boundary case (empty plans → no section) correctly passes both before and after.
Existing tests preserved: MeasurementPlanChip.test.tsx (9/9), HypothesisCardWithPlans.test.tsx (22/22),
HypothesisCardWithPlans.actionItems.test.tsx (16/16) all still PASS.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add `dueDate?: string` to `MeasurementPlan` type (ISO-8601 YYYY-MM-DD) - Add 6 i18n keys (`wall.collect.*`) to MessageCatalog + all 32 locale files - Render a `data-testid="data-collection-task"` section per plan in HypothesisCardWithPlans: header row (Assigned: collect label + status badge with `data-status` attr + optional due date) + embedded MeasurementPlanChip. The chip embedding lets `section.textContent` carry the primaryFactor + ownerName without creating duplicate `getAllByText` matches alongside the chip's own text-node span (Testing Library's `getNodeText` only reads direct text-node children). - Remove standalone chip-row map — chips are now inside each data-collection-task section; existing HypothesisCardWithPlans chip tests continue to pass since the chip is still rendered. - Section is always visible (read-display, no ACL gate). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds RED tests for the ScopeRail component (Task 5): - ScopeRail.test.tsx: 14 tests encoding the acceptance oracle — scope listing, active-scope aria-current marking, onScopeSelect switching, and onScopeArchive pruning. All fail with "Failed to resolve import ../ScopeRail" since the component doesn't exist yet. - analyzeStore.test.ts augmented: 5 new archiveScope tests (SCOPE_ARCHIVE store side-effect). All fail with "archiveScope is not a function". 108 existing store tests remain green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add archiveScope action to analyzeStore (soft-delete via deletedAt) and create ScopeRail component that lists ProblemStatementScopes as a horizontal breadcrumb rail with onScopeSelect / onScopeArchive callbacks. Reuses formatConditionLeaves from @variscout/core for chip labels. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… for acceptance Encodes the acceptance oracle for the 3 detached IM-1 flows per PLAN-im-4b.md §Task 6: Flow 1 — ImprovementIdeasSection mounted (hypothesisId-keyed): HypothesisCardWithPlans must render ImprovementIdeasSection when hub.ideas is non-empty, keyed by hypothesisId (data-testid="ideas-section-<id>"), passing ideaImpacts (impact badges) and onAddIdea affordance. Flow 2 — handleProjectIdea / ideaImpacts wired: onProjectIdea(hypothesisId, ideaId) prop threads through HypothesisCardWithPlans → ImprovementIdeasSection and fires when the "Project idea with What-If" button is clicked. Flow 3 — createHubFromFinding (propose-hypothesis-from-finding): FindingChip accepts onProposeHypothesis?: (findingId: string) => void and renders a "Propose hypothesis" affordance that fires the callback; absent prop = no button (regression guard for existing onSelect/onDetach behaviour). Regression guard: ImprovementIdeasSection direct-mount API shape verified (existing component — its tests already pass; this suite confirms the prop contract the implementer must honour when wiring it into HypothesisCardWithPlans). RED: 9 tests fail — ImprovementIdeasSection not mounted in HypothesisCardWithPlans, ideaImpacts/onProjectIdea props absent, FindingChip lacks onProposeHypothesis. 8 tests pass (negative assertions + ImprovementIdeasSection direct mount). Existing HypothesisCardWithPlans.test.tsx (22) + actionItems (16) + FindingChip (3) + HypothesisCard.test.tsx (19) all GREEN. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Flow 1 — ImprovementIdeasSection mounted in HypothesisCardWithPlans: Import ImprovementIdeasSection; render in foreignObject below the plans section when hub.ideas?.length > 0 && ideaImpacts prop is provided. Keyed by hub.id (data-testid="ideas-section-<hypothesisId>"). Passes ideaImpacts, onAddIdea, onUpdateIdea, onRemoveIdea, onSelectIdea, onProjectIdea through to the existing component — zero reinvention. Flow 2 — handleProjectIdea / ideaImpacts wired via new props: HypothesisCardWithPlansProps gains ideaImpacts, onProjectIdea, onAddIdea, onUpdateIdea, onRemoveIdea, onSelectIdea (all optional). Destructured and passed to ImprovementIdeasSection; onProjectIdea fires with (hypothesisId, ideaId) exactly as useAnalyzeOrchestration returns it. Flow 3 — createHubFromFinding CTA on FindingChip: FindingChipProps gains onProposeHypothesis?: (findingId: string) => void. When wired, renders a "Propose hypothesis" foreignObject button below the chip with stopPropagation so onSelect is not accidentally triggered. Absent prop = no button (regression-safe). Reuse: ImprovementIdeasSection (existing), FindingChip foreignObject pattern. No new HubActions, no IDB bump, no new dispatch mechanism. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ale ts-expect-error Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ustive union) Task 3 added HYPOTHESIS_ACTION_ADD/UPDATE/COMPLETE to the union but no apply-reducer cases, so both apps hit assertNever (PWA build failed; azure tsc too). Handle them as F5-deferred no-ops, mirroring HYPOTHESIS_RECORD_DISCONFIRMATION — hypothesis ActionItems are in-session-only (analyzeStore) and ride the analyze blob. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…anvas (production seam)
IM-4b adversarial review found 5/6 features GREEN-but-DEAD: the leaf
components existed + unit-tested but WallCanvas forwarded 0 of the new
callbacks, so the affordances never rendered via the production path.
Wire the seam:
- WallCanvasPlanningProps now declares the comment-thread, ActionItem,
and improvement-idea callbacks (+ ideaImpacts).
- The hubPlanningProps builder forwards all of them to the card (and via
DraggableHypothesisCard's Omit-typed planningProps, the drag path too).
- HypothesisCardWithPlans mounts HypothesisComments via a foreignObject
(presence of onAddHubComment is the enable signal; ACL gate lives inside).
- Export HypothesisComments + ScopeRail from the AnalyzeWall barrel.
- Fix the blanked {primaryFactor} placeholder in the data-collection-task
header (was shipping 'Assigned: collect '; now renders plan.primaryFactor).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + PWA apps The leaf affordances now reach the production path end-to-end: - useHypotheses gains addComment/editComment/deleteComment, addAction/ completeAction, addIdea/updateIdea/removeIdea/selectIdea — routing through the hook keeps the Wall's source of truth (hypothesesState.hubs) live in-session AND syncs useAnalyzeStore via onHubsChange (rides the analyze blob, mirroring the IM-4a disconfirmation precedent). - Azure: AnalyzeWorkspace enriches Editor's base measurement-plan bag with the comment/task/idea callbacks (sourced from hypothesesState + ideaImpacts + handleProjectIdea, which Editor now consumes) and mounts the ScopeRail above the canvas — onScopeSelect rewrites the drill filters so IM-4a's producer re-anchors the Problem card; onScopeArchive -> analyzeStore.archiveScope. - PWA: wallPlanningProps routes comment/task/idea writes through useAnalyzeStore (AnalyzeView's Wall source of truth). ScopeRail is Azure-only — the PWA Wall has no scope spine (IM-4a deferred it; PWA-Mount-Deferral). - @mentions: the composer wiring runs parseMentions(text, members) and passes mentionedUserIds to addHubComment in both apps. - recentComments: useAIContext now forwards hubComments/findingComments/ todayStartMs to buildAIContext; useAIOrchestration flattens live hub + finding comments so investigation.recentComments populates for CoScout. - Export parseMentions from the @variscout/core barrel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…il + cleanup
- Descope createHubFromFinding: remove the dead FindingChip.onProposeHypothesis
prop + the 'Propose hypothesis' button (apps defer finding->hypothesis-on-Wall
to the IM-4c bipartite re-layout). Replace the unit test with a descope guard.
- Localize ScopeRail: new wall.scope.archive i18n key (types.ts + all 33 locale
catalogs, English backfill matching the IM-4 wall-key convention); the archive
aria-label now reads from getMessage.
- ScopeRail a11y/style: fix the invalid role='button'-span-inside-button nesting
(the archive control is now a sibling <button>, absolutely positioned), and
swap raw palette classes for the semantic design tokens sibling Wall chrome uses.
- parseMentions: longest-match-wins for prefix-overlapping display names
('Bob' vs 'Bob Member') — sort longest-first + consume matched spans; + tests.
- Test hygiene: drop the dead vi.mock('@variscout/stores') blocks from
ScopeRail.test + HypothesisComments.test (neither component imports stores;
the @variscout/hooks + lucide mocks in HypothesisComments stay — FindingEditor
needs them).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ffordances
These exercise the PRODUCTION seam (not injected-prop leaf units):
- WallCanvas.collab.seam.test.tsx renders the real WallCanvas with a hub +
full planningProps and asserts the comment thread, ActionItem '+ Add Task'
rows, and ImprovementIdeasSection RENDER on the card path + DISPATCH through
the forwarded production callbacks (onAddHubComment / onAddHypothesisAction /
onCompleteHypothesisAction), plus the {primaryFactor} data-collection header.
- AnalyzeWorkspace.mapwall.test.tsx gains a ScopeRail seam block: renders the
real AnalyzeWorkspace (WallCanvas stubbed, ScopeRail real) and asserts the
rail renders from useAnalyzeStore.scopes, selecting a chip re-anchors by
rewriting analysisScopeStore.categoricalFilters, and archiving soft-deletes
via analyzeStore.archiveScope.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…der fix The data-collection-task header now interpolates plan.primaryFactor (Task 4 fix), so the factor legitimately appears in BOTH the header and the embedded chip. Update the two per-hypothesis / soft-delete plan-filter assertions in WallCanvas.test to count twice (header + chip) while still proving the filtering intent (factor absent under the non-matching hub / deleted plan). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-for-editors) ImprovementIdeasSection mounted on the hypothesis card without a canEdit gate, so a non-member viewer (members configured, current user not in the roster) got an over-permissive add/edit/remove/select/project surface — inconsistent with the sibling contribution affordances (plans, tasks, disconfirmation, comments) which all gate on edit-contributions per canAccess(). Add a canEdit prop (default true → FindingsLog-era call sites + existing tests unaffected). When false the section renders read-only: existing ideas, impacts and projections stay visible; add input / project / remove buttons hide; the timeframe select renders as a static label and the select-toggle as a non-interactive indicator. Parent (HypothesisCardWithPlans) passes its canEdit. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
IM-4b — Investigation Wall collaboration layer
Spec:
docs/superpowers/specs/2026-05-30-investigation-wall-unified-canvas-design.md§4–5, §8.7–8.9.Master plan:
docs/superpowers/plans/2026-05-29-investigation-surface-master-plan.md(IM-4b).Follows IM-4a (#256). The unified bipartite layout + Focus lens are deferred to IM-4c.
Wires existing backends (comment stack, ActionItem model, scope ops) into the production Wall seam.
What ships
HypothesisCommentsmounted on the card; human composer + edit/delete; SSE-synced; ACL-gated.AIContext.recentComments—parseMentions(longest-match) in the composer; recent hub/finding comment text now flows to CoScout (Azure; PWA has nobuildAIContextpath).HYPOTHESIS_ACTION_*union handled in both apps'applyAction.MeasurementPlan.owner/status/due shown as the data-collection task (distinct from chore(deps): bump actions/checkout from 4.2.2 to 6.0.2 #3's ActionItems).ProblemStatementScopes; switching re-anchors via IM-4a's active-scope selection; archive prunes intermediate scopes.ImprovementIdeasSection(hypothesisId-keyed) +handleProjectIdea/ideaImpactsconsumed by the Editor.Terminology
Labels surface as "Supports / Counts against" and "Contributing factors"; code identifiers keep
support/refute/tributaries.Notes / deviations
createHubFromFinding(finding→hypothesis CTA) descoped to IM-4c (needs the bipartite layout); store method + tests retained.HYPOTHESIS_*persistence.onUpdateHypothesisActionomitted (no edit-text UI — YAGNI; the union member exists + is handled as a no-op).canEdit-gated (read-for-all / write-for-editors), matching the sibling contribution affordances.Process note
This slice trialed a test-author→implementer TDD pipeline. The first pass produced green-but-dead features (leaf components unit-tested but never wired into the production
WallCanvas → card → appseam); an adversarial review caught it and a single Opus wiring pass + seam-level integration tests recovered it. Verdict: the lean TDD pipeline is the wrong tool for integration-heavy UI — leaf-scoped tests bypass the integration seam. IM-4c/IM-6 use single-implementer.Verification
bash scripts/pr-ready-check.sh— all packages build, both app vite builds, all tests).@variscout/uisuite (2776 tests),@variscout/uibuild, both-apptsc --noEmitall green.🤖 Generated with Claude Code