Skip to content

feat(sdk): WS3b — remaining SDK client primitives (useJsonFetch, ConfirmDialog, useAvailableModels, formatters, EmptyState, toneBadge)#502

Open
markhayden wants to merge 10 commits into
mainfrom
feat/sdk-gaps-remainder
Open

feat(sdk): WS3b — remaining SDK client primitives (useJsonFetch, ConfirmDialog, useAvailableModels, formatters, EmptyState, toneBadge)#502
markhayden wants to merge 10 commits into
mainfrom
feat/sdk-gaps-remainder

Conversation

@markhayden

Copy link
Copy Markdown
Owner

WS3b — the follow-up to WS3 (PR #501, A1 SSE consolidation only). Adds the remaining
client-side SDK primitives the audit found plugins reinventing, and migrates the genuine
duplicated consumers. One commit per finding; each green on typecheck.

Spec: .claude/specs/audit-2026-06/REPORT.md. Plan: tasks/plan.md (archived WS3 plan at
tasks/plan-ws3-sdk-gaps.md).

What's in it

  • A4 — formatDuration/formatDateTime (core/format → @makinbakin/sdk/utils). Migrated
    task-run-history (the canonical calendar variant) + deleted health's byte-identical formatAge;
    supplemental migration of schedule run-history's weaker formatTime. Unit-tested.
  • A8 — toneBadgeClass (SDK utils). The bg-X-500/10 text-X-400 border-X-500/20 outline-badge
    idiom across 5 tones; migrated task-run-history + schedule run-history.
  • A5 — EmptyState consolidation. Folded team's larger-chip variant into the SDK EmptyState
    behind variant='panel'; deleted the team fork. Default look unchanged (6 prior consumers intact).
  • A3 — ConfirmDialog (SDK components). Consolidated all 6 hand-rolled delete dialogs
    (schedule, tasks, workflows, assets TagFolderGrid, team-manager, agent-detail). New unit test.
  • A2 — useJsonFetch hook (cancellable JSON GET). Migrated the 3 clean-fit consumers
    (memory tier-overview-cards, team heartbeat-tab + active-context-tab). Unit-tested.
  • A6 — useAvailableModels hook (module-cached, mirrors useNotificationChannels). Migrated
    the agent-form + agent-detail model pickers.
  • A7 — task-drawer workflow types re-based on the SDK WorkflowInstance/WorkflowDefinition
    via interface extension (single-sources the wire shape; WS1 fixed instanceId).

Scope discipline

The audit's 2026-06-13 recon predated #500/#501; several counts were stale and a few "migrations"
would have changed rendering/behavior. In each case I migrated the genuine fits and documented the
deliberate exclusions
in the commit body + tasks/plan.md rather than forcing a lossy change —
e.g. 3 assets sites already on formatAge; health/models use a different border-less badge idiom;
models-page owns the live /refresh flow; the task-drawer's imperative instance fetches.

Verification

  • bun run test: 5093 pass / 0 fail (5102 across 490 files).
  • bun run typecheck: clean. bun run lint: clean (one pre-existing team-grid warning, untouched).
  • bun run build: 3 binaries; build-stamp generated files reverted (tree clean).
  • New unit tests: format, ConfirmDialog, useJsonFetch. Existing component tests (workflow-actions
    real-Radix, schedule-page, kanban-dnd, agent-detail ×5, tier-overview, etc.) pass unchanged.
  • Not run here (human merge gate): dockerized-rig Playwright page sweep — the UI-behavior gate
    per the plan. Logic is covered by the suite; visual/SSE parity wants the E2E before merge.

🤖 Generated with Claude Code

markhayden and others added 10 commits June 15, 2026 09:55
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…reimplementations

Adds formatDuration(ms) and formatDateTime(ts) to packages/core/src/format.ts
(re-exported via @makinbakin/sdk/utils next to formatAge), extracted verbatim from
task-run-history's relativeTime (the calendar variant: Today/Yesterday + short-date +
prior-year disambiguation) and its formatDuration. Migrates task-run-history onto the
shared functions and deletes the health plugin's byte-identical local formatAge.

Scope note: the audit's '7 reimpls' count predated a prior migration — the three
assets/versioned sites (atoms, VersionedAssetGrid, VersionRow) already import formatAge
from the SDK. The two remaining relative-age variants (models formatRelativeTime, team
formatRelative) are intentionally left in place: they are NOT byte-identical to formatAge
(seconds tier, date fallback) and folding them in would change rendering, violating the
'render identically' acceptance bar. They are candidates for a separate behavior-changing
pass, not this extraction.

New unit tests cover formatDuration + formatDateTime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ory maps

Adds toneBadgeClass(tone) to @makinbakin/sdk/utils — a literal-string lookup (Tailwind
@source-scanned) for the bg-X-500/10 text-X-400 border-X-500/20 outline-badge idiom across
five tones (success/pending/error/muted/info). Migrates the two sites using that exact
idiom: task-run-history's STATUS_CLASS + OUTCOME_CLASS and schedule run-history's status
map. The settled=info(blue) vs done=success(green) distinction (#476) is preserved.

Scope note: health-page's STATUS_TONE (border-less, yellow) and models-page's TIER_STYLES
(border-less, purple) use a DIFFERENT badge idiom with off-palette colors — not the
three-class outline idiom this helper encodes — so migrating them would change rendering.
Left in place; documented as a distinct idiom.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
schedule run-history's local formatTime was an 8th calendar-variant reimpl the audit's
count missed — identical to formatDateTime for current-year dates but lacking prior-year
disambiguation. Replace it with the shared formatDateTime (the strictly-better source per
WS3 A4); prior-year runs now carry the year. No change for same-year timestamps.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e fork

The team plugin maintained a forked EmptyState with a larger rounded-2xl icon chip +
semibold title (for full-tab empty surfaces) alongside a dead fillHeight prop. Fold the
visual variant into the SDK EmptyState behind a new variant='panel' prop: the default
keeps the exact original SDK look (its 6 prior consumers render unchanged), and panel
reproduces team's look exactly. Widen icon to ComponentType and description to ReactNode
(both backward-compatible supertypes). Repoint team's 3 importers (heartbeat-tab,
active-context-tab, agent-detail ×3) to @makinbakin/sdk/components with variant='panel',
and delete plugins/team/components/empty-state.tsx.

Dropped (not promoted to the published SDK): the fillHeight prop had zero importers
repo-wide — promoting dead API to the SDK surface is worse than dropping unused code.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds a controlled ConfirmDialog to @makinbakin/sdk/components — busy/error aware, with a
confirm-while-busy self-close guard — consolidating six near-identical hand-rolled delete
dialogs. Migrates all six: schedule/delete-schedule-dialog and tasks/delete-task-dialog
become thin wrappers; workflows/workflow-delete-action (keeps its trigger button +
close-on-success logic), team/team-manager, team/agent-detail, and assets TagFolderGrid
swap their inline Dialog blocks for ConfirmDialog.

Props cover every observed variation: confirmLabel, busyLabel (+ spinner), cancelVariant
(TagFolderGrid's ghost), error, confirmTestId (preserves folder-delete-confirm), and a
className width override. Busy now consistently shows a spinner + disables both buttons
(a minor enhancement for schedule/workflow, exact match for agent-detail/TagFolderGrid).

Tests: new ConfirmDialog unit test (open gating, labels, busy, error, backdrop-close
guard, testid). delete-schedule-dialog.test rewired to mock the shared ConfirmDialog.
workflow-actions (real Radix) and the dialog-mocking schedule-page/kanban-dnd tests pass
unchanged. 28 tests green across the 5 affected files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds useJsonFetch<T>(url, opts?) → { data, loading, error, refresh } to
@makinbakin/sdk/hooks — an AbortController-based cancellable JSON GET that replaces the
'let cancelled = false' fetch-in-useEffect boilerplate. url=null skips fetching; opts is
read per-request without re-triggering on identity. Unit-tested (load, skip, non-2xx,
network error, refresh).

Migrates the three clean-fit consumers: memory/tier-overview-cards (plain JSON→data) and
team's heartbeat-tab + active-context-tab (the {ok,payload,error} envelope, unwrapped via a
small derive). Existing component tests for all three pass unchanged.

Left as opportunistic (genuine non-fits for a single-URL read-only hook, noted in
tasks/plan.md): team-grid (best-effort badge, no loading/error, derived Set),
lesson-toggle-list (mutates lessons after fetch — needs writable state), overview-tab
(4-way Promise.all), and node-type-palette / assets AssetPreview / use-notification-channels
(module-cache hook) / PluginHost.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds useAvailableModels() to @makinbakin/sdk/hooks (impl in plugins/models/hooks/), a
module-cached + single-flight read-only catalog of GET /api/plugins/models/available —
mirroring the useNotificationChannels precedent (cache + test-only reset, returns the
array directly, empty until loaded). Migrates the two model-picker consumers: team's
agent-detail (clean swap) and agent-form (the /available fetch + default-model selection
splits into the hook + a set-if-empty effect; the sibling /teams fetch stays).

models-page is intentionally not migrated: it owns the live /refresh mutation flow and
keeps its own mutable state — a read-only cached hook would break refresh-updates-the-list
(the same boundary useNotificationChannels draws). Noted in tasks/plan.md.

Existing agent-detail + models-page tests pass unchanged (34 across 5 files).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
task-detail-dialog hand-rolled WorkflowInstance/WorkflowDefinition independently. Re-base
them on the SDK wire types (WS1 single-sourced the shape — instanceId, not id) via
interface extension, narrowing only the fields this component reads (typed steps[] and
stepStates) that the SDK intentionally leaves open. The base wire fields now track one
source; drift on instanceId/workflowId/taskId/status can't recur.

Left as-is (deliberate, noted in tasks/plan.md):
- The local Workflow summary type ({filename, name, stepCount}) — the definitions-list
  shape has no SDK equivalent.
- The instance/definition fetches — they are chained and imperatively re-fetched after
  approve/reject/retry; useJsonFetch (A2) is not cleaner here, and the plan marks that
  adoption optional.

typecheck + lint clean; task-detail-dialog + kanban tests pass (12).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mark all seven WS3b tasks complete with per-task scope notes, and document the new
SDK client primitives (useJsonFetch, useAvailableModels, ConfirmDialog, EmptyState panel
variant, toneBadgeClass, formatDateTime/formatDuration) in plugin-system.md's sub-path
table + a 'Shared client primitives' section.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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