Redesign v1: tokens, primitives, screens, canvas refresh#3
Merged
Conversation
Plan derived from archflow-redesign.html concept: design tokens, component inventory, 7 implementation phases (foundation → shell → overview → diagrams → canvas → feature diff → polish). Tracked as taskmaster epic redesign-v1 under phase redesign-v1 with 13 initial tasks.
Add Pill, Button, Kbd, SectionLabel, Pulse, LevelBar, Avatar, AvatarStack under components/ui/. Gallery route at /design (DEV-only) showcases every variant for visual token review. Refs plan docs/plans/redesign-v1.md task 004
Rewrite AppSidebar to Tailwind-only: 240px bg-panel, sectioned nav (Main / Workspace / Team), coral-stripe active state, gradient-avatar account block, NotificationsBell + WorkspaceSwitcher preserved. WorkspaceSwitcher restyled with design tokens: pill button, gradient workspace initial avatar, dropdown with inline rename. Add PageToolbar component — shared breadcrumb + title + actions pattern (⌘K search button, primary CTA slot). SearchButton helper included. Adopted across 11 list/dashboard pages. TopBar.tsx deleted (confirmed no remaining imports). Refs plan docs/plans/redesign-v1.md tasks 005, 006
…ity) Replace stat/list layout with hero greeting (serif-italic coral em-dash), 4-card stat grid (drafts-awaiting pink-accented), recent diagrams preview cards with canvas-bg thumbnails and status pills, activity feed, and quick-start column. Refs plan docs/plans/redesign-v1.md task 007
Two-pane layout: 260px folder sidebar with tree sections (Pinned / Packs by type / C4 Level filter via LevelBar), content area with pack header + search/filters/view-toggle toolbar, and sticky-grouped table with tinted icon wells, level bars, and status pills. Grid view reuses PreviewCard. Refs plan docs/plans/redesign-v1.md task 008
ObjectSidebar: header with SELECTED pill, sectioned body (Description / Technology stack / Tags / Connections / Owners) with wrappable pills, colored tags, selected-edge coral glow on connections list, mono footer meta with coral "draft" version. EdgeSidebar: unified Direction/Shape pill-buttons, gradient-fill slider with coral thumb, font-mono protocol input, Via pass-through pills and themed dropdown. All existing behavior (debounced label, flip visibility, ViaSelector) preserved. Refs plan docs/plans/redesign-v1.md task 011
C4Node with coral-selected double glow, type pills, mono metadata and live-edit indicator. ActorNode circle variant with purple glow. ExternalSystemNode dashed border. GroupNode re-skinned to tokens. C4Edge label chip with panel-blur background. Canvas top bar: ghost back + mono breadcrumb, DRAFT·UNSAVED pill when on a forked fork, AvatarStack presence, Search ⌘K, and Publish button. PresenceRoster consolidated into AvatarStack — old component removed. Per-node live-edit presence is wired: useDiagramSocket selections are inverted in ArchFlowCanvas into a nodeId -> user-names map on the canvas store, and each node renders a "● editing" chip when a remote peer has it selected. Refs plan docs/plans/redesign-v1.md tasks 009, 010
Panel background + rounded border + padded shadow, typed node colors (actor purple / container coral / component blue / group green / external amber), darkened mask outside viewport, coral 1.5px viewport rectangle stroke. Preserves pan/zoom interactivity. Refs plan docs/plans/redesign-v1.md task 013
Replace AddObjectToolbar with a coral-gradient 44px FAB (pulsing ring, rotate-to-× on open) that reveals a 340px popup with three staggered sections: object-pool search+list, create-new-object type grid, and annotation pills. Wraps the existing object-creation + attach mutations. Refs plan docs/plans/redesign-v1.md task 012
…corner Caller was passing an `absolute` className to AddObjectFAB, which clobbered the internal `relative` class via Tailwind ordering — so the popup's `position: absolute` resolved against the canvas wrapper instead of the FAB itself. Move the positioning wrapper outside the component and drop the `className` prop; FAB now renders as its own relative container with the popup slotting immediately to the right.
Include workspaceId in the React Query key for activity hooks so switching workspaces cleanly refetches and never serves stale cross-workspace events. (Axios interceptor already sends X-Workspace-ID; this was a cache-key bug.)
Rename sidebar label Packs → Folders. Add "+ New folder" affordance that opens a modal (name + color). Row action icons redesigned as hover-reveal 3-dot menu / pin-toggle / delete with clean 14px stroke SVGs and destructive-red on delete hover. 3-dot menu includes Move to folder with folder list; diagrams are also draggable onto folder tree items in the sidebar. Real workspace packs now shown as "Folders" primary section; synthetic type grouping kept behind an explicit label with collapse toggle.
Single dialog with name input, type grid (level-labeled cards), optional folder select, and coral Create CTA. Opens from PageToolbar actions and quick-start CTAs, closes on Esc / backdrop / success. After creation, parent handlers navigate to the new diagram.
Add a denormalized workspace_id column to activity_log (nullable, FK to workspaces with SET NULL on delete) so the global activity feed can be filtered to the caller's current workspace. - Alembic migration a1b2c3d4e5f6 (head: f1a2b3c4d5e6): adds the column, a covering index, and backfills existing rows from model_objects / diagrams / connections (via source object). - ActivityLog ORM model gains the workspace_id mapped column + index. - activity_service log_created/log_updated/log_deleted each accept an optional workspace_id kwarg and pass it through to the log entry. - object_service populates workspace_id at all three call sites (created, updated, deleted) using obj.workspace_id which is already on ModelObject. - GET /activity now depends on get_current_workspace_id (reads X-Workspace-ID header, falls back to default workspace) and filters the query; returns 400 when workspace cannot be resolved.
Original activity-workspace migration used revision id a1b2c3d4e5f6 which collided with the existing diagram_packs migration (2 heads at alembic upgrade head). Renamed to b2c3d4e5f7a8, rebased down_revision onto the real head eb9e2003d7b9 (default_shape_smoothstep). Backfill queries compared target_type against lowercase literals, but SQLAlchemy serialises the ActivityTargetType enum by .name (UPPERCASE) — the enum values in Postgres are OBJECT/DIAGRAM/CONNECTION. Fix the three WHERE clauses to match.
Replace window.prompt() in AddObjectFAB's handleCreateNew with a proper themed modal (NewObjectModal). Includes a soft duplicate-name warning shown inline under the input — non-blocking, user can proceed anyway.
Fix 1: RowMenu submenu in DiagramsPage now detects if right-flyout would clip the viewport and flips to open on the LEFT side using getBoundingClientRect + requestAnimationFrame. Fix 3: CreateChildDiagramModal footer buttons ported from inline styles to design-token Button components (ghost Cancel + primary coral Create); level/scope pill uses bg-surface/border-border-base tokens. Fix 4: AddObjectFAB wrapper moved from top-1/2 -translate-y-1/2 to top-28 (~112px from top) so the tall popup has room above the bottom tags bar on a typical 900px canvas viewport.
PreviewCard now fetches the diagram's actual nodes and connections, then renders them as a scaled-to-bbox SVG with type-colored shapes and thin edges. Falls back to the prior pre-baked per-type motif until the data loads or for empty diagrams.
Backspace-on-selected-node visually removed the node (ReactFlow's built-in key handler stripped it from the internal store) but the canvas never wired `onNodesDelete`, so no mutation ran. The row lived on in both the `objects` and `diagram-objects` caches — any subsequent cache re-read (refetch, filter toggle, a remote peer's node-position broadcast triggering an effect re-run, or the nodes-build effect firing on a later render) would re-hydrate the node from the still-populated cache. Root cause, precisely: the racing write is the nodes-build effect in CanvasInner reading `diagramObjects × allObjects` and calling `setNodes(merged)`. With no server-side delete there's nothing to filter the row out, so the next time that effect runs the node comes back. Fix, three layers: 1. Wire `onNodesDelete` → `useRemoveObjectFromDiagram.mutate` per deleted node. Removes the junction row; object stays in the workspace pool (matches standard diagramming-tool mental model). 2. Make `useRemoveObjectFromDiagram` and `useDeleteObject` patch their caches in `onMutate` (with `cancelQueries` to cut off any in-flight refetch whose stale payload would otherwise clobber the optimistic removal). This means the canvas sees the row disappear in the same tick as the keypress, without waiting for the HTTP round-trip. 3. Short-TTL tombstone set in use-realtime.ts. Guards against a real multi-user race: peer A moves a node (server fans out `diagram_object.updated`) at the same moment peer B deletes it (`diagram_object.removed` + `object.deleted`). A third client can receive `.updated` AFTER `.removed` and the naive cache merge would re-insert the row. The tombstone makes WS patchers drop stale merges for just-removed ids within 5s. Also stamped from the mutations above, so a late-arriving WS echo of the local user's own delete can't resurrect it either. No invalidateQueries added as a band-aid — the existing `onSuccess` invalidations remain and are now safe because the cache is already consistent, refetches can only reaffirm the removal, and the tombstone blocks any racing re-insert.
NewObjectModal already had a lowercased-name dup check and an inline
amber warning, but the Create button was identical whether or not
a duplicate existed — users clicked through without noticing, ending
up with two "Mongo" Stores side-by-side.
Changes:
- Expand warning copy to name the conflict and the scope explicitly
("An object named 'Mongo' already exists in this workspace.
Continue anyway?") so it reads as a confirm prompt, not a typo
hint.
- Swap the Create button label to "Create anyway" when a duplicate
is detected. Still soft-warn (not blocking) — in C4 it's legitimate
to have two Cache or Queue nodes in different contexts — but the
label makes the user consciously acknowledge the dup.
- Keep `existingNames` sourced from `useObjects(draftId)` in
AddObjectFAB, so the check is workspace-pool-wide (draft-aware).
…tical Popup now stacks as flex column: fixed header, pool scrolls within a capped max-height (280px), Create and Annotation sections always visible at the bottom. FAB button returns to its original vertical center position (the earlier "top-28" change was wrong — only the popup needed rework).
…polish Per-type gradient fills (semi-opaque top -> transparent bottom), rounded 1.6px type-color strokes with soft drop-shadow, actor radial gradient, curved edges between node-edge midpoints, increased internal padding, min node size for readability. All inline SVG - no extra fetches or deps. IntersectionObserver lazy-load preserved.
Wire GET /api/v1/auth/me to the existing get_current_user dependency so it resolves the authenticated user from JWT/API-key and returns id, email, name, and created_at. Add smoke test in tests/api/test_me.py.
Add useMe() hook (GET /api/v1/auth/me, staleTime 2 min, enabled only when authenticated) and replace the JWT-sub fallback in AppSidebar with real profile data. Avatar initials prefer name initials, fall back to email local-part. While /me is loading, shimmer placeholders replace the name/email lines so UUIDs never flash.
Popup was anchored top: 50% of FAB's wrapper so when the FAB sat below centre of the visible viewport, the popup extended off the bottom. Now use position: fixed + top: 50vh + translateY(-50%); horizontal left is computed from the FAB's bounding rect and recomputed on resize. Popup max-height bumped to min(640, 100vh-40) for more breathing room.
- Hero "Good evening, {firstName}" now reads from /me instead of decoding
JWT (which fell back to UUID "B9ea60c9" for tokens without the email
claim).
- Activity items resolve target_id against the local objects/connections/
diagrams caches and show the real name. Missing names fall back to a
plain "deleted {type}" instead of a truncated UUID like "object 216ae8".
The FAB wrapper in DiagramPage uses CSS transform (-translate-y-1/2), which creates a new containing block for any descendant position:fixed — so our viewport-centred popup was actually positioned relative to the transformed wrapper and fell off the bottom of the screen (looked like "nothing appears on click"). Render the popup through createPortal(document.body) so position:fixed resolves against the viewport as intended. Click-outside detection now checks both the FAB wrapper and the portaled popup root.
User drew the target: popup should span the full canvas vertical space alongside the FAB, not float centred. Use position:fixed with top:72 / bottom:72 so the popup auto-stretches; object-pool section is now flex-1 so it grows to fill, while Create + Annotation sections stay pinned at the bottom of the popup as before.
Small diagrams (2-3 nodes) were fitted to the viewport at a fractional zoom like 1.23x — React Flow's scale transform then pushed nodes onto fractional pixel boundaries and rendered them soft/blurry. Larger diagrams (8+ nodes) fit at zoom < 1, which happens to compose cleanly, so the blur only showed on certain diagrams. fitViewOptions maxZoom: 1 clamps auto-fit to native resolution with 0.2 padding; diagrams render crisp at 1:1 and users can still zoom in manually.
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
Full visual refresh driven by
docs/plans/redesign-v1.md(concept reference:~/Downloads/archflow-redesign.html). 31 commits, ~4k net LOC. Pure frontend + one small backend addition (/meendpoint + activity workspace scoping migration).Draft — opened for review / smoke testing before we cut a real PR.
Scope
Foundation
@themetokens:bg / panel / surface / surface-hi,border-base / border-hi, 4-step text ramp,coral / coral-2, fiveaccent-*semantics with matching-glowsiblings,shadow-window / popup / coral-glow / node-selected, keyframes (dash,pulse-dot,fab-ring,popup-in,item-stagger,fade)..page-bgradial gradients,.canvas-bgdot grid,.noisefractal overlay, themed 10px scrollbars.components/ui/):Pill+StatusPill(neutral / done / review / processing / input / draft / ai),Button(default / primary / ghost × sm/default/icon),Kbd,SectionLabel,Pulse,LevelBar,Avatar,AvatarStack. Dev-only gallery route at/design.Shell
AppSidebar— 240px panel, workspace switcher, sectioned nav (Main / Workspace / Team), coral-stripe active state, bottom account block with real name + email from/me(shimmer while loading, no UUID fallbacks).PageToolbarshared across 11 pages — mono breadcrumb + title +⌘Ksearch + optional primary CTA.Screens
DiagramPreviewSvgthumbnails, activity stream with resolved object/connection/diagram names, quick-start column.LevelBar, sticky mono group headers, tinted row icon wells, 3-dot hover menu (Rename / Pin / Move to folder / Delete) with screen-edge flip for submenus, grid/list view toggle.C4Node/ActorNode/ExternalSystemNode/GroupNode/C4Edge(type pill + dot, 15px name, mono metadata, coral-glow selected, purple radial actor), top bar with breadcrumb +DRAFT · UNSAVEDpill +AvatarStackpresence + Publish button.MiniMaptokenized with coral viewport rectangle.AddObjectFAB(pulsing coral gradient, rotate-to-×) opens a portaled popup that fills canvas vertical space with sticky Create / Annotations sections.Polish
NewDiagramModalunified across Overview + Diagrams (type grid + folder preselect).NewObjectModalreplaces nativeprompt()with themed modal + soft duplicate-name warning ("Create anyway").CreateChildDiagramModalre-skinned centered.onMutatewithcancelQueries+ 5s tombstone set inuse-realtime.ts— deleted nodes no longer resurrect on mouse move.fitViewOptions: { maxZoom: 1, padding: 0.2 }— prevents fractional auto-zoom blur on small diagrams.Backend
GET /api/v1/auth/me— reads current user (was a 401 stub).b2c3d4e5f7a8:activity_log.workspace_idcolumn + backfill + index; activity queries now scoped.Known TODOs / follow-ups
colorcolumn yet (deterministic hash fallback).CanvasCommentsflow.Test plan
/designroute: every primitive renders, hover / active states work.defaultPackId).alembic upgrade headclean from previous head.Breaking changes
None. All existing routes + APIs preserved. The
TopBarcomponent was deleted (unused afterPageToolbarrollout).