Skip to content

Redesign v1: tokens, primitives, screens, canvas refresh#3

Merged
TheAlexPG merged 31 commits intomainfrom
feat/redesign-v1
Apr 23, 2026
Merged

Redesign v1: tokens, primitives, screens, canvas refresh#3
TheAlexPG merged 31 commits intomainfrom
feat/redesign-v1

Conversation

@TheAlexPG
Copy link
Copy Markdown
Owner

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 (/me endpoint + activity workspace scoping migration).

Draft — opened for review / smoke testing before we cut a real PR.

Scope

Foundation

  • Self-hosted IBM Plex Sans Variable / Mono / Serif (italic 500) — latin + cyrillic subsets.
  • Tailwind v4 @theme tokens: bg / panel / surface / surface-hi, border-base / border-hi, 4-step text ramp, coral / coral-2, five accent-* semantics with matching -glow siblings, shadow-window / popup / coral-glow / node-selected, keyframes (dash, pulse-dot, fab-ring, popup-in, item-stagger, fade).
  • Utilities: .page-bg radial gradients, .canvas-bg dot grid, .noise fractal overlay, themed 10px scrollbars.
  • UI primitives (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).
  • PageToolbar shared across 11 pages — mono breadcrumb + title + ⌘K search + optional primary CTA.

Screens

  • Overview: hero greeting with serif-italic coral em-dash, 4-card stat grid (drafts pink-accented), real DiagramPreviewSvg thumbnails, activity stream with resolved object/connection/diagram names, quick-start column.
  • Diagrams: folder tree (real workspace packs — create / rename / delete / move / drag-drop), C4 Level filter via 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.
  • Canvas: restyled C4Node / ActorNode / ExternalSystemNode / GroupNode / C4Edge (type pill + dot, 15px name, mono metadata, coral-glow selected, purple radial actor), top bar with breadcrumb + DRAFT · UNSAVED pill + AvatarStack presence + Publish button. MiniMap tokenized 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

  • NewDiagramModal unified across Overview + Diagrams (type grid + folder preselect).
  • NewObjectModal replaces native prompt() with themed modal + soft duplicate-name warning ("Create anyway").
  • CreateChildDiagramModal re-skinned centered.
  • Delete key: optimistic onMutate with cancelQueries + 5s tombstone set in use-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).
  • Alembic b2c3d4e5f7a8: activity_log.workspace_id column + backfill + index; activity queries now scoped.

Known TODOs / follow-ups

  • Pack color: UI picker exists, backend has no color column yet (deterministic hash fallback).
  • Activity feed: still labeled "You" for all actors (no display names on log entries yet).
  • "Add annotation" buttons functional but rely on existing CanvasComments flow.
  • Owners section in inspector — placeholder (no members picker API).

Test plan

  • /design route: every primitive renders, hover / active states work.
  • Login flow unchanged; sidebar shows real name + email.
  • Overview hero greeting shows first name, not UUID.
  • Overview activity stream shows resolved names (or "deleted {type}").
  • Diagrams page: create folder, move diagram via 3-dot or drag-drop, rename / delete folder, C4 Level filter, grid/list toggle.
  • New Diagram modal: Overview + Diagrams + inside a folder (defaultPackId).
  • Canvas: FAB → popup stretches full canvas height, object pool scrolls, Create + Annotations always visible; Escape / backdrop closes.
  • Canvas: drag-create node, rename, delete via Backspace, verify it doesn't reappear.
  • Canvas: duplicate object name shows amber warning with "Create anyway".
  • Canvas: drill-down from System node opens themed centered modal.
  • Canvas: MiniMap reads panel tokens, viewport rect coral.
  • Canvas: nodes crisp at default fitView (no blur on sparse diagrams).
  • Workspace switch: activity stream refetches; no cross-workspace leak.
  • Alembic: alembic upgrade head clean from previous head.

Breaking changes

None. All existing routes + APIs preserved. The TopBar component was deleted (unused after PageToolbar rollout).

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.
- Self-hosted IBM Plex Sans Variable + Mono 400/500/600 + Serif 500-italic
- @theme block with full color/shadow/font/animation token set
- .page-bg radial gradients, .canvas-bg dot grid, .noise overlay,
  themed 10px scrollbars in @layer utilities
- Refs plan docs/plans/redesign-v1.md tasks 001-003
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.
@TheAlexPG TheAlexPG marked this pull request as ready for review April 23, 2026 20:30
@TheAlexPG TheAlexPG merged commit d248118 into main Apr 23, 2026
3 checks passed
@TheAlexPG TheAlexPG deleted the feat/redesign-v1 branch May 4, 2026 18:35
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