Skip to content

fix(web): real ops board, no fixture data, Guides - #94

Merged
jusso-dev merged 1 commit into
mainfrom
fix/os-real-data-board-guides
Jul 29, 2026
Merged

fix(web): real ops board, no fixture data, Guides#94
jusso-dev merged 1 commit into
mainfrom
fix/os-real-data-board-guides

Conversation

@jusso-dev

@jusso-dev jusso-dev commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

  • No fixture product data: Teams and Capabilities show empty states (no seeded SOC teams / fake skill catalogue).
  • Operations board: defaults to board mode with drag-and-drop; status updates via PATCH /api/v1/tasks/:id.
  • Guides: new /guides section (nav + palette) explaining real vs empty data, SoR boundaries, and how to use Command/Operations/Approvals.

Homelab note

Archived 28 synthetic-titled tasks on the private homelab so the queue is not full of e2e leftovers.

Test plan

  • apps/web unit tests
  • typecheck
  • CI green
  • Homelab redeploy

Summary by CodeRabbit

  • New Features

    • Added a Guides section with navigable documentation and quick links to key areas.
    • Added Guides to primary navigation and the command palette.
    • Operations board cards can now be moved between status columns with saved updates and error feedback.
  • Updates

    • Replaced sample teams and capabilities with clear empty states until governed data is available.
    • Improved task status handling and board filtering.
  • Tests

    • Added coverage for Guides, Operations, and fixture-free empty states.

Stop showing fixture teams/capabilities as live catalogues. Default
Operations to a drag-and-drop board that PATCHes real task status.
Add Guides for product orientation (Muster vs SoR, empty states, usage).
@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Guides route with shell and command-palette navigation, introduces selectable static guide content, connects Operations board drag-and-drop to task PATCH updates, and replaces fixture-backed Teams and Capabilities views with governed empty states.

Changes

Company OS surfaces

Layer / File(s) Summary
Guides route and navigation
apps/web/app/guides/page.tsx, apps/web/features/guides/*, apps/web/components/os/*
Adds selectable guide articles, the /guides route, sidebar navigation, command-palette access, and source-marker tests.
Live Operations board updates
apps/web/lib/api/client.ts, apps/web/lib/queries/hooks.ts, apps/web/features/operations/*
Maps live task statuses to board columns and persists drag-and-drop moves through PATCH requests with query invalidation and status feedback.
Fixture-free Teams and Capabilities
apps/web/features/teams/teams-view.tsx, apps/web/features/capabilities/capabilities-view.tsx, apps/web/lib/api/fixtures/fixtures.test.ts
Replaces fixture-backed displays with governed empty states and verifies fixture data is not referenced by those views.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • jusso-dev/Muster#93: Adds overlapping Guides navigation and command-palette entries in the Company OS foundation.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately reflects the main changes: real operations data, removal of fixture data, and the new Guides section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/os-real-data-board-guides

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/web/features/operations/operations-view.tsx (1)

63-96: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Status badge/board column loses fidelity for non-canonical statuses.

status is computed from the narrowed rawStatus (toOperationalState(rawStatus)) instead of the original task.status. Since asTaskStatus collapses anything outside {backlog, ready, in_progress, review, done} to "backlog", any task actually in states like blocked, waiting, failed, cancelled, or completed/closed/resolved (all explicitly handled by toOperationalState) will be shown as queued and dropped into the Backlog column — misrepresenting its real state in both the board and the status badge used in list view/detail drawer.

🐛 Proposed fix — derive the badge status from the original value
-  const rawStatus = asTaskStatus(task.status);
   return {
     ...
-    status: toOperationalState(rawStatus),
-    rawStatus,
+    status: toOperationalState(task.status),
+    rawStatus: asTaskStatus(task.status),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/features/operations/operations-view.tsx` around lines 63 - 96,
Update taskToBoardItem so the status field passed to toOperationalState is
derived from the original task.status rather than the narrowed rawStatus from
asTaskStatus. Preserve rawStatus for the canonical raw-status field, while
allowing explicitly supported non-canonical values such as blocked, waiting,
failed, cancelled, completed, closed, and resolved to retain their operational
state in the board and status badge.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/web/features/guides/guides-view.tsx`:
- Around line 125-133: Add an aria-pressed attribute to each guide button in the
guide list, deriving its boolean value from the same active?.id === guide.id
condition used by className. Preserve the existing click behavior and styling
while exposing the selected guide state to assistive technology.

In `@apps/web/features/operations/operations-view.tsx`:
- Around line 220-311: Add a keyboard- and assistive-technology-operable status
control to each task card in the board rendering near the existing drag
handlers, such as a “Move to…” select or button group. Populate it from
TASK_COLUMNS, invoke the existing moveTask function with the task ID and
selected column ID, and retain the current drag-and-drop behavior.
- Around line 133-144: Prevent overlapping status updates for the same task in
moveTask by tracking task IDs with in-flight mutations and returning early when
that ID is already pending; clear the tracking state in all completion paths.
Also use updateTask.isPending to disable cards’ draggable behavior while a move
is active, providing feedback and preventing re-drag attempts.

---

Outside diff comments:
In `@apps/web/features/operations/operations-view.tsx`:
- Around line 63-96: Update taskToBoardItem so the status field passed to
toOperationalState is derived from the original task.status rather than the
narrowed rawStatus from asTaskStatus. Preserve rawStatus for the canonical
raw-status field, while allowing explicitly supported non-canonical values such
as blocked, waiting, failed, cancelled, completed, closed, and resolved to
retain their operational state in the board and status badge.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a580d492-4012-406e-94cc-6f4f96a6a650

📥 Commits

Reviewing files that changed from the base of the PR and between ffff990 and df2be93.

📒 Files selected for processing (13)
  • apps/web/app/guides/page.tsx
  • apps/web/components/os/company-os-shell.test.ts
  • apps/web/components/os/company-os-shell.tsx
  • apps/web/components/os/os-command-palette.tsx
  • apps/web/features/capabilities/capabilities-view.tsx
  • apps/web/features/guides/guides-view.test.ts
  • apps/web/features/guides/guides-view.tsx
  • apps/web/features/operations/operations-view.test.ts
  • apps/web/features/operations/operations-view.tsx
  • apps/web/features/teams/teams-view.tsx
  • apps/web/lib/api/client.ts
  • apps/web/lib/api/fixtures/fixtures.test.ts
  • apps/web/lib/queries/hooks.ts

Comment on lines +125 to +133
<button
type="button"
onClick={() => setActiveId(guide.id)}
className={cn(
"flex w-full items-start gap-2 rounded-md px-2 py-2 text-left text-sm",
active?.id === guide.id
? "bg-muted font-semibold text-foreground"
: "text-muted-foreground hover:bg-muted/60 hover:text-foreground",
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Expose the selected guide state to assistive technology.

The active guide is indicated only through className, so screen readers cannot identify the current selection. Add aria-pressed (or implement tabs with aria-selected) to each guide button.

Proposed fix
                 <button
                   type="button"
+                  aria-pressed={active?.id === guide.id}
                   onClick={() => setActiveId(guide.id)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button
type="button"
onClick={() => setActiveId(guide.id)}
className={cn(
"flex w-full items-start gap-2 rounded-md px-2 py-2 text-left text-sm",
active?.id === guide.id
? "bg-muted font-semibold text-foreground"
: "text-muted-foreground hover:bg-muted/60 hover:text-foreground",
)}
<button
type="button"
aria-pressed={active?.id === guide.id}
onClick={() => setActiveId(guide.id)}
className={cn(
"flex w-full items-start gap-2 rounded-md px-2 py-2 text-left text-sm",
active?.id === guide.id
? "bg-muted font-semibold text-foreground"
: "text-muted-foreground hover:bg-muted/60 hover:text-foreground",
)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/features/guides/guides-view.tsx` around lines 125 - 133, Add an
aria-pressed attribute to each guide button in the guide list, deriving its
boolean value from the same active?.id === guide.id condition used by className.
Preserve the existing click behavior and styling while exposing the selected
guide state to assistive technology.

Comment on lines +133 to +144
async function moveTask(id: string, status: TaskStatusId) {
setMoveError(null);
const current = items.find((item) => item.id === id);
if (!current || current.rawStatus === status) return;
try {
await updateTask.mutateAsync({ id, status });
} catch (error) {
setMoveError(
error instanceof Error ? error.message : "Could not update task 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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

No guard against overlapping status-update requests for the same task.

Dragging the same card again before a prior updateTask.mutateAsync resolves fires a second concurrent PATCH with no ordering guarantee — the completion order can differ from call order, so a stale response can arrive after a newer one and leave the wrong status persisted. The no-op check on line 136 uses the pre-mutation items snapshot and won't prevent this race.

🔒 Proposed fix — block re-drag while a move is in flight
   async function moveTask(id: string, status: TaskStatusId) {
     setMoveError(null);
+    if (updateTask.isPending) return;
     const current = items.find((item) => item.id === id);
     if (!current || current.rawStatus === status) return;

Consider also disabling draggable on cards while updateTask.isPending is true so users get visual feedback instead of a silently dropped move.

Per TanStack Query, the order in which mutations are fulfilled may differ from the order of mutate function calls. Community discussion on the same pattern confirms PATCH requests must not overtake each other.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async function moveTask(id: string, status: TaskStatusId) {
setMoveError(null);
const current = items.find((item) => item.id === id);
if (!current || current.rawStatus === status) return;
try {
await updateTask.mutateAsync({ id, status });
} catch (error) {
setMoveError(
error instanceof Error ? error.message : "Could not update task status.",
);
}
}
async function moveTask(id: string, status: TaskStatusId) {
setMoveError(null);
if (updateTask.isPending) return;
const current = items.find((item) => item.id === id);
if (!current || current.rawStatus === status) return;
try {
await updateTask.mutateAsync({ id, status });
} catch (error) {
setMoveError(
error instanceof Error ? error.message : "Could not update task status.",
);
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/features/operations/operations-view.tsx` around lines 133 - 144,
Prevent overlapping status updates for the same task in moveTask by tracking
task IDs with in-flight mutations and returning early when that ID is already
pending; clear the tracking state in all completion paths. Also use
updateTask.isPending to disable cards’ draggable behavior while a move is
active, providing feedback and preventing re-drag attempts.

Comment on lines +220 to +311
{mode === "board" && filtered.length > 0 ? (
<div className="overflow-x-auto pb-2">
<div className="grid min-w-max grid-cols-5 gap-3">
{TASK_COLUMNS.map((column) => {
const columnItems = filtered.filter(
(item) => item.rawStatus === column.id,
);
return (
<section
key={column.id}
aria-label={`${column.label} column`}
className={cn(
"w-[17rem] rounded-md border bg-card",
dragOverColumn === column.id &&
"border-[var(--color-accent)] bg-muted/40",
)}
onDragOver={(event) => {
event.preventDefault();
event.dataTransfer.dropEffect = "move";
setDragOverColumn(column.id);
}}
onDragLeave={() =>
setDragOverColumn((current) =>
current === column.id ? null : current,
)
}
onDrop={(event) => {
event.preventDefault();
setDragOverColumn(null);
const id = event.dataTransfer.getData("text/task-id");
if (id) void moveTask(id, column.id);
}}
>
<header className="flex items-center gap-2 border-b border-border px-2 py-2">
<div className="min-w-0 flex-1">
<h2 className="text-xs font-semibold uppercase tracking-[0.06em]">
{column.label}
</h2>
<p className="text-xs text-muted-foreground">
{column.hint}
</p>
</div>
<Badge className="bg-muted text-muted-foreground">
{columnItems.length}
</Badge>
</header>
<ul className="min-h-28 space-y-2 p-2">
{columnItems.map((item) => (
<li key={item.id}>
<article
draggable
onDragStart={(event) => {
event.dataTransfer.effectAllowed = "move";
event.dataTransfer.setData("text/task-id", item.id);
}}
onClick={() => setSelectedId(item.id)}
className={cn(
"group w-full cursor-grab rounded-md border border-border bg-[var(--color-paper)] p-2 text-left active:cursor-grabbing",
selectedId === item.id && "border-[var(--color-accent)]",
)}
>
<div className="flex items-start gap-1.5">
<GripVertical
className="mt-0.5 size-3.5 shrink-0 text-muted-foreground opacity-60 group-hover:opacity-100"
aria-hidden
/>
<div className="min-w-0 flex-1">
<p className="text-xs font-semibold leading-snug">
{item.title}
</p>
<div className="mt-1 flex flex-wrap gap-1">
<SeverityBadge severity={item.severity} compact />
<span className="text-xs text-muted-foreground">
{item.systemOfRecord}
</span>
</div>
<p className="mt-1 text-xs text-muted-foreground">
{item.ownerName ?? "Unassigned"} ·{" "}
{relativeTime(item.updatedAt)}
</p>
</div>
</div>
</article>
</li>
))}
</ul>
</section>
);
})}
</div>
</div>
) : null}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Drag-and-drop is the only way to change task status — no keyboard-accessible fallback.

The board relies exclusively on native drag-and-drop (draggable, onDragStart/onDrop) to move cards between columns; there's no button, select, or other keyboard/assistive-technology-operable control to change a task's status anywhere in the view (list mode has none either). This blocks a core workflow for keyboard-only and screen-reader users.

Suggest adding a lightweight fallback, e.g. a per-card "Move to…" select or button group calling the same moveTask, so status changes remain possible without drag gestures.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/web/features/operations/operations-view.tsx` around lines 220 - 311, Add
a keyboard- and assistive-technology-operable status control to each task card
in the board rendering near the existing drag handlers, such as a “Move to…”
select or button group. Populate it from TASK_COLUMNS, invoke the existing
moveTask function with the task ID and selected column ID, and retain the
current drag-and-drop behavior.

@jusso-dev
jusso-dev merged commit ded9a5c into main Jul 29, 2026
8 checks passed
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