feat(wedge): PR-WV1-3 — membership lifecycle + ActionItem CRUD (3a) + MeasurementPlan + Wall (3b)#186
Merged
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
eb28ad6 to
a6bed5c
Compare
…tion INVITATION_ACCEPT synthesizes a ProjectMember from the invitation's fields (userId, displayName, role, invitedAt) plus acceptedAt as createdAt, using generateDeterministicId for the new member id. INVITATION_REVOKE is a no-op at the members[] level — invitation status transitions are store-layer concern.
…target IP acceptInvite is now a composite action: it looks up the Invitation, dispatches INVITATION_ACCEPT through reduceProjectMembers (which synthesizes a ProjectMember via generateDeterministicId), and calls upsertProject on useImprovementProjectStore to patch the target IP — all before filtering the invite from pendingInvites. revokeInvite remains filter-only. Also exports reduceProjectMembers + MembershipAction from @variscout/core/projectMembership index (they were defined but not re-exported).
PWA: reads pendingInvites/acceptInvite/revokeInvite from useProjectMembershipStore (selectors) and renders the banner at the top of HomeScreen — visible to users who haven't loaded data yet. Azure: same selector pattern; banner mounts in the layout chrome between InvestigationMetadataPanel and the main tab-content div (visible across all active views, renders null when no invites). Tests: new HomeScreen.test.tsx (2 assertions); Azure Editor.test.tsx extended with 2 banner-integration assertions + ui mock switched to importOriginal to expose real PendingInvitesBanner.
…Home view Users with loaded data on the Home tab now see pending invitations; previously only the empty-state HomeScreen path surfaced the banner. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ceActionItems - Add optional `actions?: ActionItem[]` field to ImprovementProjectMetadata (read-write via reduceActionItems; optional preserves existing fixtures) - Export `reduceActionItems` and `ActionItemPatch` as values from @variscout/core/actions barrel so app layers can import without reaching into the internal module path Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…re.upsertProject
Replace 3 console.warn stubs in PWA ImprovementView and Azure Editor with
real ACTION_ITEM_ADD / UPDATE / REMOVE dispatch:
- Extract `buildApplyAction(activeIP, upsertProject)` pure helper in
ImprovementView.tsx (exported for unit testing)
- PWA ImprovementView: adds useImprovementProjectStore selector, reads
actions from activeIP.metadata.actions, calls applyAction on each callback
- PWA App.tsx: drops the now-unnecessary `actions={[]}` prop
- Azure Editor.tsx: inline applyAction pattern mirrors PWA, currentUserId
from currentUser?.email
- Tests: ImprovementView.applyAction.test.ts covers null-guard, ADD, UPDATE,
REMOVE without full component rendering
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…pplyAction Both Dexie persistence layers (PWA + Azure) were missing ACTION_ITEM_UPDATE and ACTION_ITEM_REMOVE cases, causing assertNever() to throw at tsc build time when the new ActionItemAction kinds (added in Task 2) hit the default branch. Fixes the build-breaking TS error at applyAction.ts:589. Also fixes the vitest Mock<> type annotation in ImprovementView.applyAction .test.ts — vi.fn() typed as ReturnType<typeof vi.fn> failed tsc strict check; switched to `Mock<(project: ImprovementProject) => void>` from vitest import.
Creates @variscout/core/measurementPlan with MeasurementPlan, MeasurementMethod, MeasurementPlanStatus types per wedge spec §3.6.3. Paired package.json#exports + tsconfig.json#paths per CLAUDE.md invariant. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…tPlanReadAPI - Extend HubAction union with MeasurementPlanAction (4 variants) - Add MeasurementPlanReadAPI interface to HubRepository.ts - Add measurementPlans field to HubRepository interface - Export MeasurementPlanReadAPI from persistence barrel
…th apps
- Azure schema v12: add measurementPlans table (id, hypothesisId, status, deletedAt)
- PWA schema v5: add measurementPlans table (same indexes)
- Add MEASUREMENT_PLAN_{ADD,UPDATE,REMOVE,LINK_FINDING} cases to both applyAction.ts
- Add measurementPlans.{get,listByHypothesis} to AzureHubRepository + PwaHubRepository
- TDD: 4 new tests per app (ADD/UPDATE/REMOVE/LINK_FINDING) + exhaustiveness coverage
Pure DOM components (no SVG) for MeasurementPlan display and creation inside HypothesisCard — TDD with 13 tests (7 chip + 6 form), barrel-exported from InvestigationWall index. Task 8 will mount via foreignObject. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
DOM component that filters findings by hypothesis ownership and not-yet-linked status, then emits onConfirm(ids[]) for Task 8 to dispatch MEASUREMENT_PLAN_LINK_FINDING. 6 tests green.
…oreignObject Strategy B wrapper: HypothesisCardWithPlans renders HypothesisCard unchanged plus a foreignObject extension zone below it for MeasurementPlanChip rows, + Add Plan button (ACL-gated), AddPlanForm inline expansion, and LinkFindingPicker overlay. Callbacks bubble up to parent for dispatch. 18 TDD tests, open-access escape for empty-members V1 scenario. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ch (PWA + Azure) Adds WallCanvasPlanningProps bag to WallCanvas (1 optional prop vs 7 individual), updates DraggableHypothesisCard to conditionally render HypothesisCardWithPlans, threads planningProps through InvestigationView (PWA) and InvestigationWorkspace (Azure), and wires MEASUREMENT_PLAN_ADD + MEASUREMENT_PLAN_LINK_FINDING dispatch with optimistic state updates in App.tsx and Editor.tsx. Plans loaded reactively via useEffect from Dexie repository per hypothesis. IDs stamped with generateDeterministicId(). 56 tests green across ui, pwa, azure-app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ith actual types Removed excess fields (type/hypothesisId/validationStatus/tags/updatedAt/ investigationId) that Task 8/9a test fixtures incorrectly included; added the required Finding shape (context, evidenceType, status, comments, statusChangedAt, investigationId). Caught by pnpm --filter @variscout/ui build (tsc) per feedback_ui_build_before_merge — tests passed at runtime but the build's stricter object-literal narrowing rejected excess properties. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tegration) Pins the Wall-surface inline-only decision and documents the V1 limitations (no edit form, currentUserId hardcoded for PWA, etc.) so future PRs know which items still owe forward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…PendingInvitesBanner project name - MeasurementPlanChip: add role="button" + aria-label when canEdit=true (ARIA spec permits role="button" on div; no nested-button violation since button is a child element, not a nested button element). Drop both when canEdit=false. TDD: 2 new tests. - AddPlanForm: filter soft-deleted members (deletedAt !== null) from the eligible owners list alongside the existing sponsor-role filter. TDD: 1 new test. - PendingInvitesBanner: add optional resolveProjectName prop; render resolved name with UUID fallback. Wire in all 3 mount sites (PWA App.tsx active-IP path, PWA HomeScreen no-data path, Azure Editor.tsx). TDD: 2 new tests. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…error rollback + plan-load deps key
PWA + Azure parallel changes:
- Fix 1 (PWA only): wrap wallActiveIPMembers in useMemo to stabilize reference and avoid downstream wallPlanningProps useMemo invalidation on every render.
- Fix 4: onLinkFinding optimistic update deduplicates linkedFindingIds — fast double-tap no longer produces phantom duplicate rows. Reducer already deduped; UI now matches.
- Fix 5: plan-loading useEffect deps keyed on hypothesisIds.join('|') instead of the array reference. Each hypotheses store mutation was creating a new array reference → N serial Dexie reads per mutation. Also switched serial for-loop to Promise.all(flat) for cleaner parallel fetch.
- Fix 6: dispatch calls no longer void-ed. onAddPlan rolls back optimistic state on rejection (filter out stamped id). onLinkFinding captures snapshot before update and restores it on rejection. No test — error path; rollback hardening per code review.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…2 deferrals - docs/investigations.md: add 5 entries surfaced by PR-WV1-3 architecture review: 1. [RESOLVED] HubAction-vs-Project-metadata-patch dispatch rule — own-Dexie-table entities use HubRepository.dispatch; metadata-bag fields use upsertProject. Dead-code ACTION_ITEM_UPDATE/REMOVE in applyAction.ts documented as intentional. 2. [LOGGED] V2 ACL gap: pendingInvites recipientUserId not filtered per recipient — dormant in V1 single-user PWA; PR-WV1-5 auth-wiring closes it. 3. [LOGGED] 3 test gaps: MEASUREMENT_PLAN_REMOVE E2E, Dexie upgrade smoke, acceptInvite-missing-project guard. 4. [LOGGED] PWA_SINGLE_USER_ID: 'analyst@local' hardcoded in 3 places; consolidate to packages/core/src/identity/pwaSingleUser.ts in PR-WV1-5. - packages/ui/CLAUDE.md: add "Test fixtures" section — require factory functions (createFinding, createHypothesis) over bare typed literals; document that tsc catches drift vitest misses. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
26f6326 to
db8112c
Compare
11 tasks
This was referenced May 17, 2026
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.
Summary
PR-WV1-3 of the wedge V1 implementation. Two phases on one branch: cleanup (3a) shipped first, MeasurementPlan + Wall integration (3b) layered on top. Single squash-merge.
Phase 3a — Invitation lifecycle + ActionItem CRUD (commits
be2b5085→00f7a272)Invitation lifecycle (closes PR-WV1-1 deferred item a)
MembershipActionextended withINVITATION_ACCEPT(composite — synthesizes aProjectMemberfrom the Invitation viareduceProjectMembers) +INVITATION_REVOKE.useProjectMembershipStore.acceptInvitere-wired: finds the target IP viauseImprovementProjectStore.projectsByHub, appliesreduceProjectMemberswithINVITATION_ACCEPT, dispatches viaupsertProject, AND filters frompendingInvites[]. Composite + atomic.reduceProjectMembers+MembershipAction+ProjectMemberPatchnow re-exported from@variscout/core/projectMembership.ActionItem CRUD (closes PR-WV1-2 Task 2 deferred)
ACTION_ITEM_UPDATE+ACTION_ITEM_REMOVEadded toActionItemAction. NewreduceActionItemsreducer covers all 3 kinds.REMOVEis soft-delete (setsdeletedAt: removedAt).ActionItemPatchomitsid | createdAt | deletedAt | parentImprovementProjectIdperfeedback_action_patch_omit_lifecycle.ImprovementProjectMetadata.actions?: ActionItem[]field added.<ImprovementView>consumers replaced theirconsole.warnstubs with real dispatches throughuseImprovementProjectStore.upsertProject(...)+reduceActionItems. PurebuildApplyActionhelper extracted.applyAction.tsexhaustive switches updated.PendingInvitesBanner on Home
<PendingInvitesBanner>atpackages/ui/src/components/Home/. Rendersnullwhen zero invites.HomeScreen.tsxempty welcome state + PWAApp.tsxHome view + AzureEditor.tsx.Phase 3b — MeasurementPlan sub-entity + Wall integration (commits
f6f40507→e8e165e8)Data model
MeasurementPlanentity:hypothesisId,factor,method: 'sensor' | 'manual-count' | 'gemba-walk' | 'expert-assessment' | 'other',sampleSize,owner: ProjectMember['id'],status: 'planned' | 'in-progress' | 'complete' | 'skipped',linkedFindingIds?,msaRequired?. ExtendsEntityBase.@variscout/core/measurementPlansub-path (pairedpackage.json#exports+tsconfig.json#paths).MeasurementPlanAction4-variant union (ADD / UPDATE / REMOVE / LINK_FINDING) +reduceMeasurementPlansexhaustive reducer.MeasurementPlanPatch = Partial<Omit<MeasurementPlan, 'id' | 'createdAt' | 'deletedAt' | 'hypothesisId'>>perfeedback_action_patch_omit_lifecycle.Hypothesis.measurementPlanIds?: string[]added (parallel tofindingIds;string[]notMeasurementPlan['id'][]to avoid circular import).HubActionunion extended withMeasurementPlanAction.MeasurementPlanReadAPIinterface added toHubRepository(get+listByHypothesis).Persistence
measurementPlanstable indexedid, hypothesisId, status, deletedAt.applyAction.tsextended with 4MEASUREMENT_PLAN_*cases (beforeINVESTIGATION_*group). Soft-delete viadeletedAtperfeedback_strict_assert_over_silent_migration.measurementPlans.get+measurementPlans.listByHypothesis(hypothesisId)filteringdeletedAt === null.UI components (all DOM, mounted via
foreignObject)<MeasurementPlanChip>— DOM row with status indicator amber⏳/green✓/gray✗.<AddPlanForm>— inline expansion form; owner picker filtersrole !== 'sponsor'per spec §"Permissions". Defaults:method='sensor',sampleSize=30,status='planned'.<LinkFindingPicker>— modal-style multi-select; filtershypothesis.findingIds.includes(f.id) && !plan.linkedFindingIds?.includes(f.id). (Findinghas nohypothesisIdfield — spec's example filter adapted.)<HypothesisCardWithPlans>— wraps<HypothesisCard>and renders the plans extension viaforeignObject. Strategy B chosen (wrapper, not extend) because HypothesisCard already at 13 props. Local ReactuseStateforaddPlanFormOpen+linkFindingForPlanId— matches theBrushToFindingFlowpattern. ACL:canEdit = members.length === 0 || (currentUserId !== null && canAccess(currentUserId, members, 'edit-approach')).Wall + app wiring
<WallCanvas>gained optionalplanningProps: WallCanvasPlanningPropsbag (plans, members, currentUserId, 3 callbacks).<DraggableHypothesisCard>conditionally rendersHypothesisCardWithPlansvsHypothesisCard.App.tsx+ AzureEditor.tsxload plans viauseEffectfrom the new repository accessor, buildwallPlanningPropsuseMemo withMEASUREMENT_PLAN_ADD(id stamped viagenerateDeterministicId()— neverMath.random) + per-idMEASUREMENT_PLAN_LINK_FINDINGdispatch + optimistic state updates.onEditPlan=console.warnpass-through (edit UI deferred to V2).Architectural decision pinned
Task 5 discovery confirmed the spec's §3 "no separate modal, sidebar, or panel" —
<HypothesisCard>already usesforeignObjectfor DOM-in-SVG content (mini-chart, TagChip rows, OneStepAwayBadge);<BrushToFindingFlow>is the inline-confirmation precedent. The earlier "HypothesisDetailPanel" naming in the plan was a transitional artifact. Decision logged indocs/investigations.md2026-05-16 entry.V1 limitations carried forward
useProjectMembershipStorepersistence key → PR-WV1-5.<MeasurementPlanChip>edit form: V1 pass-through (console.warn).currentUserIdhardcoded'analyst@local'until real auth wiring lands.Test plan
@variscout/core— 3455/3455 green@variscout/stores— 281/281 green@variscout/pwamapwall + applyAction targeted — 98/98 green@variscout/azure-appmapwall + applyAction targeted — 123/123 green@variscout/uiInvestigationWall (30 files) — 206/206 greenpnpm --filter @variscout/ui build— green (caught and fixed test fixture type drift before push perfeedback_ui_build_before_merge)pnpm --filter @variscout/pwa build— greenpnpm --filter @variscout/azure-app build— green--chromebrowser walk: open Wall → add a Plan on a Hypothesis → mark complete → link to a FindingCanonical artifacts
docs/superpowers/specs/2026-05-16-pr-wv1-3-measurement-plans-design.mddocs/superpowers/plans/2026-05-16-pr-wv1-3a-membership-cleanup.mddocs/superpowers/plans/2026-05-16-pr-wv1-3b-measurement-plans-wall.mddocs/investigations.md2026-05-16 entrydocs/superpowers/plans/2026-05-16-wedge-implementation.md(PR-WV1-3 row)docs/decision-log.mdNext
PR-WV1-4 (canvas response paths 5 → 3 + persona-routing deletion) fans out off main after this lands.