Skip to content

WH-058: Post-creation action affordances for cards#73

Open
edmofro wants to merge 19 commits intomainfrom
workhorse/wh-058-spec
Open

WH-058: Post-creation action affordances for cards#73
edmofro wants to merge 19 commits intomainfrom
workhorse/wh-058-spec

Conversation

@edmofro
Copy link
Copy Markdown
Member

@edmofro edmofro commented Apr 3, 2026

Summary

On card creation, want to be able to get straight into something like "create and implement", "create and fix", "create and draft specs", "create and begin interview", "create and workshop ideas", etc.

Should this be an affordance? Should the create card button have a split dropper that has alternative actions? Should there be auto detection of what you might want to do and it just starts that? Workshop this with me. Maybe it's like somewhere between a checkbox "get started" to the left of "create card", but where get started can be changed to start with different things. Don't like the words get started btw, don't use them. Still needs workshopping

Journey

  • Workshop started — exploring post-creation action affordances and design approaches for card workflow
  • Post-creation action mockup created — skill selector pill + Create card button compound control
  • Post-creation action mockup refined — compared split button vs. pill selector approach; Option A (pill selector) preferred to avoid ⌘↵ ambiguity
  • Post-creation action UI exploration — four layout options for pill-based affordances (inside box, same row, bottom strip, floating)
  • Direction shift — exploring metadata/property affordances integrated into card creation screen alongside post-creation actions
  • Design direction refined — property strip placement confirmed (above, separate); icon treatment standardized (unset = property name + placeholder icon; set = value + representative icon); action affordance language reconsidered (pills read as toggles, not actions)
  • Post-creation action mockup refined — Option 1 confirmed (pill selector); creation modal, in-card mid-conversation, and fresh card states added showing skill affordance variations and property strip integration
  • Post-creation action mockup aligned with design system — send button accent color, status dots (amber/blue), radius-pill 24px, focus shadows, chat typography, and avatar styles now match globals.css and current code
  • Post-creation action mockup enhanced — cmd+N keyboard hints now reveal on ⌘ hold instead of always visible, reducing visual clutter
  • Post-creation action mockup annotated — clarified distinction between journey dropdown (expected forward sequence) and chat pills (contextual right-now options)
  • Spec updates initiated — rewriting journey bar design, removing property strip from card view, adding unified bar to workspace level
  • Spec updates completed — journey bar redesigned, property strip removed from card view, unified bar added to workspace level
  • Implementation started — tracing data flow for property strip and journey bar integration
  • Implementation progressing — tracing component hierarchy and data flow for PropertiesBar integration with CardWorkspace
  • Implementation in progress — verifying data flow for PropertiesBar integration; checking card.dependsOn shape and team prop compatibility with component expectations
  • Implementation cleanup — verified CardTab prop removal, JourneyBar import cleanup, and TypeScript compatibility across CardWorkspace and CardDetailPage
  • Design audit started — found 3 issues: bar height off grid (38px → 36px), invalid Tailwind duration class (duration-120 → duration-100), progress dots need grid alignment
  • Design audit continued — fixing typography (section labels 10px/bold → 11px/semibold), separator margins (my-[6px] → my-2), and verifying remaining off-grid values are intentional (progress dots, dropdown dimensions)
  • Implementation verification completed — CardTab cleanup confirmed, PropertiesBar rendering verified across chat and artifact views

Specs

  • .workhorse/specs/card-navigation.md
  • .workhorse/specs/card-tab.md
  • .workhorse/specs/workflow-orchestration.md
  • .workhorse/specs/agent-sdk-session.md
  • .workhorse/specs/auto-review.md
  • .workhorse/specs/overview.md

Changed files

  • src/components/card/CardWorkspace.tsx
  • src/components/card/PropertiesBar.tsx
  • src/components/card/CardDetailPage.tsx
  • src/components/card/CardTab.tsx

Test plan

Review Hero

  • Run Review Hero
  • Auto-fix review suggestions
  • Auto-fix CI failures

const journeyMenuRef = useRef<HTMLDivElement>(null)
const [journeyPos, setJourneyPos] = useState<{ top: number; right: number } | null>(null)

const statusOptions = useMemo<PropertyOption[]>(() => (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bugs & Correctness] critical

Local state (status, priority, teamId, assigneeId) is initialised from card props but never syncs when props change. If the parent re-fetches the card (e.g. after another user edits it, or after navigation), the PropertiesBar will show stale values. The old CardTab had the same pattern, but now that these controls live in a persistent bar (not unmounted on tab switch), the staleness window is wider. Add useEffect hooks to sync local state when card.status, card.priority, etc. change, or key the component on card.id so it remounts on card change.

) {
setJourneyOpen(false)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bugs & Correctness] suggestion

The handleUpdate function fires-and-forgets the server action inside startTransition with no error handling. If updateCard throws (network error, validation failure), the local state will have already been optimistically updated but the user gets no feedback that the save failed. Consider catching the error and reverting local state, or showing a toast.


const hasJourney = journalEntries.length > 0 || activeStep !== null

return (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bugs & Correctness] suggestion

journeyPos is calculated once when the dropdown opens and never recalculated on scroll or resize. If the page scrolls or the window resizes while the portal dropdown is open, it will be mispositioned. The old JourneyBar avoided this by using a relative overlay rather than a fixed-position portal. Consider adding a scroll/resize listener that updates journeyPos, or use a relative positioning strategy.

@@ -0,0 +1,388 @@
'use client'
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Design & Architecture] suggestion

PropertiesBar merges two unrelated concerns: card property editing (status/priority/team/assignee) and journey progress tracking (journal entries, scheduled steps, suggestions). These have different data dependencies, different interaction patterns, and different reasons to change. The old design had JourneyBar as its own component — the property dropdowns should similarly be their own component, with PropertiesBar composing both. As-is, this 388-line file will grow every time either concern evolves.

const [status, setStatus] = useState(card.status)
const [priority, setPriority] = useState(card.priority)
const [teamId, setTeamId] = useState(card.team.id)
const [assigneeId, setAssigneeId] = useState(card.assignee?.id ?? '')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Design & Architecture] suggestion

The local state for status/priority/teamId/assigneeId duplicates server state and is updated optimistically via handleUpdate, but there's no error handling — if updateCard fails, the UI shows stale local state that diverged from the database. The old CardTab had the same pattern, but moving it to a persistent bar (visible across all views) increases the blast radius. Consider either adding rollback on failure or using the server response to reconcile.

</div>
)
}
export {}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Design & Architecture] suggestion

Replacing the entire module body with export {} leaves a dead file in the tree. If JourneyBar is no longer used, delete the file outright rather than leaving a hollow export that will confuse future readers and show up in import autocompletion.

<PropertyDropdown
trigger={
<>
<StatusDot state={statusToDotState(status)} />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Project Conventions] suggestion

The bar height h-9 (36px) is not on the 4px spacing grid defined in the design system (base unit: 4px, all values multiples of 4). Should be h-8 (32px) or h-10 (40px). Given topbar is 52px and this is a compact properties strip, h-8 (32px) is likely the right fit.

@review-hero
Copy link
Copy Markdown

review-hero bot commented Apr 3, 2026

🦸 Review Hero Summary
9 agents reviewed this PR | 1 critical | 6 suggestions | 2 nitpicks | Filtering: consensus 3 voters, 3 below threshold

Below consensus threshold (3 unique issues not confirmed by majority)
Location Agent Severity Comment
src/components/card/CardWorkspace.tsx:576 Design & Architecture nitpick PropertiesBar is rendered in two separate places (line 579 and line 764) with identical props. Extract the props into a variable or render the bar once in a shared layout position to avoid the dupl...
src/components/card/PropertiesBar.tsx:55 Project Conventions suggestion The team type is { id: string; name: string } but the API (card-detail/route.ts:94) and query hooks (queries.ts) include colour on team objects using Australian spelling (`{ id: string; n...
src/components/card/PropertiesBar.tsx:265 Project Conventions nitpick pb-[2px] on the section label is off the 4px grid. Use pb-0 or pb-1 (4px) instead.

Nitpicks

File Line Agent Comment
src/components/card/PropertiesBar.tsx 134 Design & Architecture The journey dropdown positioning logic (manual getBoundingClientRect + createPortal + click-outside + Escape handling) reimplements what PropertyDropdown/usePortalMenu already does. Consider reusing that hook or extracting a shared 'anchored portal dropdown' primitive to avoid maintaining two par...
src/components/card/PropertiesBar.tsx 207 Project Conventions The progress dots use w-[6px] h-[6px] which is not on the 4px spacing grid. Consider w-1 h-1 (4px) or w-2 h-2 (8px) to stay aligned with the design system's base-4 spacing.
Local fix prompt (copy to your coding agent)

Fix these issues identified on the pull request. One commit per issue fixed.


src/components/card/PropertiesBar.tsx:89: Local state (status, priority, teamId, assigneeId) is initialised from card props but never syncs when props change. If the parent re-fetches the card (e.g. after another user edits it, or after navigation), the PropertiesBar will show stale values. The old CardTab had the same pattern, but now that these controls live in a persistent bar (not unmounted on tab switch), the staleness window is wider. Add useEffect hooks to sync local state when card.status, card.priority, etc. change, or key the component on card.id so it remounts on card change.


src/components/card/PropertiesBar.tsx:127: The handleUpdate function fires-and-forgets the server action inside startTransition with no error handling. If updateCard throws (network error, validation failure), the local state will have already been optimistically updated but the user gets no feedback that the save failed. Consider catching the error and reverting local state, or showing a toast.


src/components/card/PropertiesBar.tsx:152: journeyPos is calculated once when the dropdown opens and never recalculated on scroll or resize. If the page scrolls or the window resizes while the portal dropdown is open, it will be mispositioned. The old JourneyBar avoided this by using a relative overlay rather than a fixed-position portal. Consider adding a scroll/resize listener that updates journeyPos, or use a relative positioning strategy.


src/components/card/PropertiesBar.tsx:1: PropertiesBar merges two unrelated concerns: card property editing (status/priority/team/assignee) and journey progress tracking (journal entries, scheduled steps, suggestions). These have different data dependencies, different interaction patterns, and different reasons to change. The old design had JourneyBar as its own component — the property dropdowns should similarly be their own component, with PropertiesBar composing both. As-is, this 388-line file will grow every time either concern evolves.


src/components/card/PropertiesBar.tsx:80: The local state for status/priority/teamId/assigneeId duplicates server state and is updated optimistically via handleUpdate, but there's no error handling — if updateCard fails, the UI shows stale local state that diverged from the database. The old CardTab had the same pattern, but moving it to a persistent bar (visible across all views) increases the blast radius. Consider either adding rollback on failure or using the server response to reconcile.


src/components/card/JourneyBar.tsx:1: Replacing the entire module body with export {} leaves a dead file in the tree. If JourneyBar is no longer used, delete the file outright rather than leaving a hollow export that will confuse future readers and show up in import autocompletion.


src/components/card/PropertiesBar.tsx:134: The journey dropdown positioning logic (manual getBoundingClientRect + createPortal + click-outside + Escape handling) reimplements what PropertyDropdown/usePortalMenu already does. Consider reusing that hook or extracting a shared 'anchored portal dropdown' primitive to avoid maintaining two parallel dropdown implementations.


src/components/card/PropertiesBar.tsx:207: The progress dots use w-[6px] h-[6px] which is not on the 4px spacing grid. Consider w-1 h-1 (4px) or w-2 h-2 (8px) to stay aligned with the design system's base-4 spacing.


src/components/card/PropertiesBar.tsx:160: The bar height h-9 (36px) is not on the 4px spacing grid defined in the design system (base unit: 4px, all values multiples of 4). Should be h-8 (32px) or h-10 (40px). Given topbar is 52px and this is a compact properties strip, h-8 (32px) is likely the right fit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant