Skip to content

Windows parity: Canvas loop cards match the macOS card (layout, states, roles, colours) #764

Open

Description

@coneilen

Visual and functional parity work item, from the 2026-10-13 side-by-side review.

  • Comparison: macOS reference Run A (app at 09eda11a, Dark, 2x) against the Windows testing-gaps run (installed 0.1.78-windows.beta26, 2560x1440 at 100%).
  • Ground truth: macOS is the reference. Only platform chrome may differ: the title bar, window buttons, menu-bar location and native file pickers.
  • Evidence limits:
    • File names below refer to the two local evidence bundles. The next macOS ground-truth run will attach captures.
    • Difference IDs (D1, E2, …) refer to the review report.
    • The reviewer opened the paired images it cites. Some surfaces could not be reviewed; they are listed under "Captures needed" in the ledger entry.
  • Done when: every acceptance box is met and shown with paired screenshots in the PR. Before/after Windows captures go next to the macOS capture, and a reviewer from a different model vendor confirms them. A UIA readback or green unit test alone does not satisfy a visual criterion.

Evidence:

  • macOS: 740/740-parityref-above-threshold-8-linked-canvas, 727/727-canvas-final-states;
  • Windows: tg720-project-canvas-1440, tg727-728-canvas;
  • differences D1–D10;
  • source: GraphCanvas.zig:1660-1700, 1899-1904, DesignTokens.zig:101-104.

Acceptance criteria:

  • A card at 100% matches macOS: 250x96 pt (LoopCardView.Metrics.size, graphcode/Sources/Features/Canvas/LoopCardView.swift:47; the 727 capture is at 68% zoom) with a semibold title, a right-aligned state pill (IDLE / DONE / STOPPED / NEEDS YOU / RUNNING / BLOCKED), a mono detail line and a mono meta line ("■ · · ").
  • Type stripes and dots use the macOS LoopTypeAppearance accents: Main #d3dae3, Goal #199e70, Timed #c98500, Turn #d55181, Composite #9085e9. Use one shared table for the canvas, sidebar, loop bar, jump palette and New Loop tiles.
  • Entry loops show the green "entry" chip, and the ENTRY marker node with lines to the entry ports appears as in 727-canvas-final-states.
  • Unwired cards show an inline "Wire it up" button and a "Mark as entry" link. The "No connections · right-click to recover" text is removed.
  • Loops awaiting input show an amber pill, an amber glow and an "Inspect" or "Reply" button whenever the rail or toolbar counts them.
  • Cards are laid out by flow (ENTRY → Main → … → leaves, with sketches stacked under ENTRY), and the canvas opens fitted to content.
  • Port dots appear on the card edges, and the project group frame carries the "N worktrees · N reclaimable" chip.
  • The add action is "+ New Node", outlined, at the top right.

Activity

  1. added
    enhancementNew feature or request
    parityWindows/macOS UI parity ledger row (investigation/ui-parity-matrix.md)
    P1 layoutParity priority 1: layout, clipping, overlap, broken rendering vs macOS
    visual parityVisual fidelity against the macOS reference captures
    on Oct 14, 2026
  2. coneilen commented on Oct 14, 2026

    @coneilen
    CollaboratorAuthor

    Correction (coordinator, 2026-10-14): the card size at 100% is 250x96 pt, per \LoopCardView.Metrics.size\ in \graphcode/Sources/Features/Canvas/LoopCardView.swift:47. The earlier about-200x75 figure was measured from a 68%-zoom capture. Lane B2 caught this.

  3. coneilen commented on Oct 14, 2026

    @coneilen
    CollaboratorAuthor

    Escalation record (wave 1, lane B), 2026-10-14 04:45 PDT.

    Old model gpt-6.1-sol, high, long_context (lane B2)
    Trigger 2 substantive code-review rejections (reviewer claude-sonnet-5.5), per the abandon rule in investigation/windows-parity-orchestration-plan.md
    New model claude-sonnet-5.5, xhigh, long_context (lane B3); reviewer gpt-6.1-sol

    State handed over: frozen head 717e8599 on fix-canvas-card-layout, not rebased; no PR opened.

    Reviewer-accepted work:

    • 250x96 shipping metrics;
    • semibold title;
    • right-aligned state pill;
    • mono detail and meta lines;
    • card-action UIA nodes from shared painted rects;
    • the DPI test row;
    • reclaim and pill-rect geometry;
    • the UTF-8 ownership fix.

    Open findings, which B3 must close test-first:

    • NB1: DT_PATH_ELLIPSIS does not middle-elide forward-slash branch metadata, so backend and age get hard-clipped. Needs a measured manual middle ellipsis.
    • N8: the font-cache sweep asserts an absolute bound on a process-global cache. It should assert growth.
    • N9: fixed 256-byte and 128-byte buffers truncate a long activity line (losing its pass prefix) and a huge metric.

    Coordinator decisions:

    • investigation/visual-baseline/manifest.json keeps 250x106. It's a historical pin at ece55b6, where macOS really was 250x106.
    • The 250x96 target is cited from the current LoopCardView.swift:47.
    • Card meta drops tokens and model tier, matching macOS; the ledger row will be corrected after merge.
  4. coneilen commented on Oct 14, 2026

    @coneilen
    CollaboratorAuthor

    Wave 1 stopped (operator decision, 2026-10-14). No PR was opened; B3 reached e63c06fe locally. All this work is abandoned.

    Decisions and findings to reuse in wave 2:

    • Card size: 250x96 is the shipping card size (LoopCardView.swift:47). Leave the 250x106 entry in investigation/visual-baseline/manifest.json alone: it's a historical pin at ece55b6.
    • Meta font: for the macOS .system(size: 10.5, design: .monospaced), use Consolas.
    • Long branch names: DT_PATH_ELLIPSIS doesn't middle-elide branch names that contain forward slashes. Use a measured middle ellipsis, so that the backend and age are never clipped.
    • Fixed buffers: 256-byte activity and 128-byte metric buffers truncate long text.
    • Font cache: it's process-global, so test bounded growth, not an absolute count.
    • Card meta: drop tokens and model tier, because macOS omits them.

    Restarting from main under investigation/windows-parity-lane-kickoff.md.

  5. coneilen commented on Oct 14, 2026

    @coneilen
    CollaboratorAuthor

    Implementation plan (wave 2)

    Planning worker pb, lane B. Source baseline: origin/main and this worktree both at 8ea81062a6220e1d2ecfdaa8a73d8eb59ac575b4. Read-only investigation; no product edits, builds, desktop use, or runtime claims.

    Complexity rating

    Rating: highly complex. This assessment covers only the compact-card slice approved in the coordinator corrections: GraphCanvas.zig, card-local DesignTokens.zig, the existing single AppFont.zig cache, and the project-card action block in App.zig's syncAccessibilityTo plus its never-shown regression. The denied ledger edit is excluded.

    The reason is the many interdependent steps across large files criterion, not the total issue size or a new general-purpose UI framework: the project and overview currently have different renderers and card dimensions; making both consume one compact body couples font measurement to title/pill allocation, owned Unicode text to measured middle elision, and overview dimensions to lane/content extents. Shrinking the card also moves existing actionable rectangles, so paint, pointer hit tests and App UIA publication must change consistently without breaking identities, DPI conversion or reclaim safety. The shared process-lifetime font-cache extension must preserve handles borrowed by unrelated native controls while adding face/weight keys and bounded-growth behavior. These coupled geometry, ownership and publication changes span the five implementation steps below even after removing all ledger work. This is more than an isolated size, label or drawing adjustment. Boxes 2-8 and every other exclusion in the coordinator correction remain outside this assessment and implementation.

    Prior decisions verified

    Read Wave 1 stopped first, then the earlier review/escalation findings. The abandoned heads are not implementation starting points.

    • Shipping cards are 250 x 96 logical points, radius 11, stripe 4: graphcode/Sources/Features/Canvas/LoopCardView.swift:46-52. I also read git show ece55b6:graphcode/Sources/Features/Canvas/LoopCardView.swift: that historical version really says 250 x 106. Do not edit investigation/visual-baseline/manifest.json:70.
    • Use Consolas for the 10.5-point monospaced card rows, as directed in the stopped comment. Title is system 14.5 semibold (LoopCardView.swift:127-135), not the existing regular Windows 14.
    • Metadata excludes tokens and model tier. Actual macOS order is type, optional branch, non-default backend display name, age (LoopCardPresentation.swift:233-241), prefixed by a drawn six-point type square (LoopCardView.swift:265-278). claudeCode is the default and is omitted; do not replace it with an always-present "Claude Code" badge.
    • Keep long strings owned and complete until measured drawing. Main already has a 128-byte metadata writer that returns a partial prefix on overflow (GraphCanvas.zig:1802-1843); do not replace it with 256-byte activity / 128-byte metric buffers and reproduce wave 1's truncation.
    • Use measured middle elision, not DT_PATH_ELLIPSIS, for slash-containing branch names. Reserve the backend and age before allocating branch width. The Windows main renderer uses tail ellipsis, so the specific path-ellipsis failure is a retained review finding, not a new runtime reproduction in this planning session.
    • AppFont.zig:26-58 is a process-global, process-lifetime cache. Cache tests must measure growth from a recorded starting point, then zero additional growth on a repeated identical sweep, not an absolute final count.

    All eight acceptance boxes: source comparison and cause

    Line references below are against the baseline above. mac means graphcode/Sources/Features/; win means graphcode-windows/src/.

    Box Exact macOS behavior Current Windows behavior Gap and root cause
    1. Card metrics, title, state pill, mono detail/meta mac/Canvas/LoopCardView.swift:46-52,64-85,127-175,265-278,354-380: 250x96, 8-point top/bottom and 11-point horizontal padding, six-point row spacing, semibold title and right-aligned fixed-content pill; mono single-line live/detail/meta. mac/Canvas/LoopCardPresentation.swift:108-160,175-180,233-241,272-281: activity with pass prefix before type-specific fallback, human action before metric progress, ordered metadata, real coarse duration. mac/App/LoopStateAppearance.swift:31-42,49-53,88-121: uppercase state vocabulary, hollow/solid indicators, distinct fill/ink; Timed idle is SCHEDULED, not IDLE. win/DesignTokens.zig:102-105 is 250x106. win/GraphCanvas.zig:1655-1718 paints title, raw lowercase state on a separate row, variable extra START/UNWIRED row, then rows at state+20/+35/+43. drawText/drawTextRect select regular Segoe (2445-2485). nodeMetadata (1802-1843) mixes pass/metric/raw backend/coarse age/tokens/branch/tier in one 128-byte line. Overview separately paints 220x86 cards with just title/raw state (809-853,903-919). drawNode and drawOverview have independent renderers; the geometry reserves a whole row for state and never measures a title/pill pair. For an entry at 100%, metadata starts at y=90 inside a 106-high card; simply changing the height clips it. nodePrimaryDetail prioritizes handed goal over live activity; metadata has neither a type label nor backend presentation mapping, and the fixed stream silently returns truncated data. This is the primary slice.
    2. Shared type colors mac/App/LoopTypeAppearance.swift:43-47 defines Main #d3dae3, Goal #199e70, Timed #c98500, Turn #d55181, Composite #9085e9. win/GraphCanvas.zig:1903-1909 has wrong canvas colors and no Timed case; its panel-only table (2148-2154) is already correct. Sidebar.zig:485-490 and TerminalSurface.zig:7169-7174 have other wrong tables. JumpPalette.zig:1323-1329 is already correct. NativeForms.zig:229-234 has correct tile literals. Duplicated tables drifted; some are older palettes, not just RGB/COLORREF byte swaps. Full fix must move the existing correct mapping into one shared Windows table and convert every listed caller, retaining serialized sketch/composite/proactive aliases. Canvas-only recoloring cannot close this box. Deferred to its own scoped follow-up; no accidental palette sweep in this slice.
    3. Entry chip and ENTRY origin/tethers mac/Canvas/CardEntryRole.swift:33-48,55-70 derives roles and draws green lowercase entry; mac/Canvas/LoopCardView.swift:112-125,293-297 draws leading-edge ports/chips. The ENTRY origin still exists: mac/Canvas/CanvasBand.swift:129-194 draws the centered origin dot, ENTRY word, and quiet tethers; Project/ProjectCanvasView.swift:360-385,399-421 supplies ports and band. Do not mistake older "marker replaced" comments for executable behavior. win/GraphCanvas.zig:1683-1684,1937-1949 draws START/UNWIRED as upper labels and has no cycle-only role. paint project arm (691-706) draws edges/cards but no group band, origin or tethers. nodeRole only checks inbound/outbound and declared entries; no band/origin layer exists. Follow-up needs role, group geometry and origin routing together; painting an unrelated START dot is not the fix. Deferred.
    4. Inline unwired recovery mac/Canvas/LoopCardView.swift:65-75,248-262: inline Wire it up button and Mark as entry link replace normal live/detail rows, while meta remains. mac/Project/ProjectCanvasCards.swift:40-41 starts wiring / dispatches marking. Overview's same verbs navigate to that loop's folder (mac/Overview/GraphOverviewCards.swift:179-185). win/GraphCanvas.zig:1697-1698 draws the old sentence. Actions exist only in context menus; win/App.zig:4390-4424 already implements session entry acknowledgment and beginWireSelectedNode. Missing painted rects, hit tests and UIA inline actions, not missing daemon commands. Follow-up must reuse the existing actions and add pointer/UIA routing; do not just replace the sentence with nonfunctional button-shaped paint. Deferred.
    5. Attention agrees with rail/toolbar, amber pill/glow/action mac/Project/ProjectCanvasView.swift:43-61,75-80 builds one rollup/reason map for rail and card tint. LoopCardView.swift:58-60,86-107,167-183 uses it for surface/border/glow; LoopCardPresentation.swift:155-160,175-180 chooses Reply for awaiting input and Inspect for blocked. GraphcodeKit/Sources/Domain/LoopNode.swift:506-525 projects live presence before stored state, including awaiting input on a resolved/blocked node. win/GraphModel.zig:1325-1341,1379-1389 counts awaitingInput presence regardless of stored state. win/GraphCanvas.zig:1911-1934 only notices awaitingInput when state is running, and separately approximates stranded blocked using handoff kind, unlike GraphModel's blocks_target path. cardTextLayout:2050-2064 hides actions under 75% zoom; drawNode always uses ordinary flat card fill. Overview has no attention actions. Two inconsistent attention predicates explain the reported succeeded/blocked card versus nonzero rail count. No attention surface/glow is painted, and UIA probes a bottom-right point rather than querying the actual presentation (App.zig:7852-7853). A faithful repair needs one authoritative reason/identity path and action availability independent of the count's display location. This slice adds correct pill words for the fields it already receives and preserves/repositions existing project buttons, but does not claim to close box 5: authoritative rollup, glow and overview actions remain a separate change.
    6. Flow layout and initial fit mac/Canvas/LaneLayout.swift:20-36,39-115,146-200 lays out starts/chains by graph depth and puts loose loops in a block below chains, with wrapping row budgets; mac/Project/ProjectCanvasView.swift:185-215 measures full extents and fits only while untouched. win/GraphCanvas.zig:1881-1887 uses index %3 and /3 with absolute sidebar/header offsets. overviewCardBounds:903-919 also grids by index. contentSize:1020-1036 uses counts, not actual moved-node extents. CanvasState.fit:370-381 exists but is not an opening-layout policy; App invokes it for explicit Fit (6810-6816). The position source ignores edges and fit ignores actual positions. Requires shared topology placement, moved-node identity preservation, content extents and initial-fit lifecycle; merely shrinking cards is not topology or fit. Deferred; do not alter zoom steps/grid tokens (#781).
    7. Edge ports plus group/worktree chip Ports and card half-outside placement: mac/Canvas/LoopCardView.swift:112-125. Group band follows card extents: mac/Canvas/CanvasBand.swift:41-59; worktree chip is absent until nonzero stats and reads count plus nonzero reclaimable (66-80); project supplies it outside composites (ProjectCanvasView.swift:388-421). win/GraphCanvas.zig:1638-1653 computes connector points, but drawNode:1706-1710 only paints an outgoing hover square. projectWorktreeChip:412-449 already has count/reclaimable text, but paints at fixed viewport-top bounds and no enclosing band. Endpoint math is not painted ports; viewport-pinned chip has no group frame or transform relationship. Deferred with band/origin layout. Preserve current chip action/count logic.
    8. + New Node add control mac/Canvas/CanvasAddButton.swift:17-39 is a dark, bordered, radius-8 control, 30 high, plus glyph plus 12-point semibold New Node. mac/Project/ProjectCanvasView.swift:103-110 anchors it top right. win/App.zig:7055-7059,7063-7112 creates/moves an ordinary native light New Loop button; it also shares its handle/state with empty global and Quick Chat actions. This is an App-owned native control, not paint in GraphCanvas. Follow-up must change label/style/layout without breaking empty/Quick Chat/workspace visibility. Deferred, needs separately approved App scope.

    Ledger rows read: Graph overview/project canvas and Loop authoring sections, especially Loop card identity/live detail/attention, Unwired card recovery, Folder lanes/bands and New Loop canvas button. They remain Partial. The live-detail row currently lists tokens/model tier as requirements/evidence; the stopped-comment decision explicitly supersedes that card-meta wording, not their availability in workspace details.

    First PR: compact, readable card presentation only

    Target: acceptance box 1's card presentation at 100% in both project canvas and global overview. Share the card body instead of fixing only project cards and leaving the overview's second renderer behind. No product feature deletion.

    To leave unchecked: boxes 2-8. In particular, a NEEDS YOU pill in this PR is not proof of attention-rollup parity or glow; START/UNWIRED roles and their context-menu recovery remain until their follow-up; no entry marker, topology, band, add-button or cross-surface palette work. Quick Chat cards are not loop cards and remain unchanged.

    Box 1 is a target, not pre-approved completion. Check it only after its focused assertions and paired image review pass. The full issue stays open (Refs #764). If the reviewer requires a presently undecoded macOS field to satisfy the box, leave it partial and request a named decoder extension rather than silently inventing text or broadening the PR.

    Implementation steps and RED/GREEN contracts

    All test names below are proposed, not tests I executed. Use existing registered roots, no new root/harness requirement. Start with passing-compilation tests against current entry points. If a seam needs extraction, first make a behavior-preserving extraction that still calls the current implementation, then obtain the assertion failure; a missing helper/compiler error does not count as RED.

    1. Metrics and container-derived geometry.

      • Files/functions: DesignTokens.zig card height only; GraphCanvas.zig CardTextLayout, cardTextLayout, nodeBounds, overviewCardBounds, overviewLaneHeight and contentSize where overview pitches/extents need to accommodate the larger 250x96 card.
      • Change: derive a compact card layout from the actual card rect and zoom: 11-point horizontal and 8-point vertical insets, bounded title/pill row, optional single-line live row, separate detail/action row, bottom-anchored meta row. Reserve the measured pill width before ellipsizing the title. Do not retain state-as-second-row spacing. Existing START/UNWIRED information and the unwired recovery sentence must fit the optional body area in bounded rects, not add an extra top row or collide with the title/pill/meta; their replacement by entry chips/inline actions is deferred. Scale from logical geometry exactly once. Keep project positions, drag offsets and selection behavior; overview needs a nonoverlapping pitch for 250-wide cards, not the current 246 pitch. Adjust its lane/content extents consistently without porting topology.
      • Test root GraphCanvas.zig, loop card parity metrics and rows fit the owning card: at zoom 1 assert project and overview widths=250, heights=96; bounds for title/pill/live/detail/meta inside the content inset; pairwise row separation and title-right <= pill-left-gap. Include long title and NEEDS YOU / SCHEDULED labels, translated x >1200, zoom 0.55/0.75/1/1.25/1.8 and nonzero pan. Assert adjacent overview cards and following lane do not overlap.
      • Expected baseline RED: height 106, overview 220x86, no reserved title/pill row, and the entry metadata position fails the 96-high containment check. Use literal target metrics, not expectations calculated from the wrong token. Existing minimum-zoom assertion at GraphCanvas.zig:2913-2919 must be updated intentionally, not merely have its old literal 106 hidden.
    2. Card-specific fonts through the one AppFont cache.

      • File/functions: AppFont.zig CacheEntry / getScaled and additive style accessors; GraphCanvas.zig card text drawing. Do not globally turn every drawText into semibold or change dialog/sidebar fonts.
      • Change: preserve existing public Segoe (size,bold) behavior; extend that same cache with face and actual weight in its key. Add regular Consolas for live/detail/meta, semibold Segoe title, bold Segoe state label. Make GDI's integral-height rounding of 14.5/10.5 logical sizes explicit and test it; do not apply monitor DPI inside the logical mapped canvas a second time (App.zig:9115-9127). Retain cached handle ownership for process lifetime; do not add a second renderer-local cache or a cache-full path leaking a freshly-created font on every paint.
      • Test root AppFont.zig, loop card parity font faces weights and cache growth: inspect LOGFONTW with GetObjectW for family, weight and rounded height; preserve identity of existing regular/semibold API calls. Record initial cache size, sweep the finite card-size/style keys for zoom 0.55 through 1.8 and DPI 96/120/144/192, assert growth bounded by the number of unique requested keys, then repeat and assert zero further growth / same handles. Exercise existing callers' keys before the sweep. If storage needs more capacity, size it to the measured finite key set and test capacity handling; never evict an HFONT still borrowed by native controls.
      • Expected baseline RED: live/meta draw with Segoe rather than Consolas; no distinct bold pill weight. A cache-growth test against a new style path is a regression guard for the implementation, not evidence that the current two-field cache already exhibits wave 1's absolute-count-test failure.
    3. Owned presentation strings and measured elision.

      • File/functions: GraphCanvas.zig nodePrimaryDetail, nodeMetadata, writeMetricChange, card-specific age/presentation helpers and the shared card renderer called by drawNode / drawOverview.
      • Change: use the existing Node fields. Prefer collapsed activity with pass N when reported; otherwise use the type's existing first instruction / goal / prompt / check / composite pilot data, without manufacturing daemon readings. Keep metric readings/change in a dedicated detail row instead of metadata. Keep complete allocated UTF-8/UTF-16 strings through measuring/drawing, and unwind ownership/errors explicitly; return no success-shaped truncated prefix.
      • Meta: drawn square, type display name (Main/Goal/Timed/Turn/Composite), branch when provided, non-default display-name backend, accurate coarse age from an injected now in whole Unix seconds. Known backend names come from GraphcodeKit/Sources/Domain/BackendCapabilities.swift:70-79; reuse an existing suitable mapper if available, otherwise a card-local mapping is sufficient for this slice. Unknown wire identifiers must not be relabeled as a different known backend. Preserve non-card formatTokenUsage and elapsedLabel behavior used in loopDetailFooter / attention rail; give card formatting its own accurate duration rather than changing unrelated surfaces.
      • Middle elision: measure with the selected Consolas font; reserve type, separators, backend and age first, then elide the branch in the remaining measured width, preserving its head/tail and Unicode boundaries. No DT_PATH_ELLIPSIS, no byte-count approximations, no arbitrary string-length buffers. Tail-elide a live line only at render time. If the non-elidable fields alone cannot fit, report that exact case to the coordinator rather than clipping backend/age and claiming success.
      • Test root GraphCanvas.zig, loop card parity live and meta content: exact output parts for all five types, default/non-default backend, branch/no branch, real 0s/59s/1m/59m/1h/1d 2h boundaries; no tokens/tier/raw backend. Assert activity wins over a populated goal and includes the pass prefix; whitespace collapses. Separate metric 1->3/maximize and 3->1/minimize with at least two samples, no invented zero-sample trend. Update existing card-only metadata assertions (3111-3172) without removing detail-panel token assertions.
      • Expected baseline RED: nodePrimaryDetail returns goal instead of reported activity; metadata lacks type, uses copilotCLI, includes tokens/tier, and has <1h/>1h instead of actual duration.
      • Test root GraphCanvas.zig, loop card parity long Unicode text and middle elision: activity >256 bytes, branch >128 bytes with forward slashes and non-ASCII/supplementary characters, nonintegral huge finite metric (for example 1e150), and allocation-failure unwinding. Verify full owned source strings before elision, valid UTF-8/UTF-16 after elision, positive measured text counts, branch head+tail, total measured width <= available width and intact type/backend/age. Expected baseline RED: its 128-byte metadata stream loses the suffix; the current whole-line tail-ellipsis paint does not retain it. Re-run the same production paint into a hidden DIB so an unused "correct" formatter cannot pass while drawNode keeps the old path.
    4. One body renderer, uppercase right-hand pill.

      • File/functions: GraphCanvas.zig drawNode, drawOverview, card-specific state presentation and paint helpers.
      • Change: delegate project/overview card text and pill to one body renderer accepting the actual bounds; keep surface-specific selection, roles and existing controls outside it. Include a measured pill with state dot/ring, bold word, correct source-derived fill/ink. Cover IDLE, RUNNING, WAITING, NEEDS YOU, BLOCKED, DONE, FAILED, STALLED, STOPPED and Timed SCHEDULED; do not implement only the six examples in the issue. Prefer busy/awaiting-input presence over stored state for the supported decoded fields, and show running+idle presence as idle. Do not mutate authoritative model state to obtain a visual word. Do not use stateColor's R/B-swapped old COLORREF literals as the new palette.
      • Keep this a presentation change. Full absent-session/exit-code/dependent-aware LoopNode.displayState is not representable by GraphModel.Node:6-35 today; no decoder or lifecycle inference is authorized in this PR. Record that limit, and escalate with the exact field if it blocks box 1's review.
      • Test root GraphCanvas.zig, loop card parity pill words indicators and title allocation: table of supported state/type/presence combinations, including succeeded+awaitingInput and blocked+awaitingInput, running+idle and timed idle; word/ink/fill/ring assertions; measured pill remains right-aligned and title is the only truncated title-row text. Expected baseline RED: raw succeeded/idle and no pill; existing draw calls select regular font. Use memory-DC paint with positive ink/dot/fill checks in named subrects, not "some pixel changed anywhere".
      • Do not paint new dead overview buttons. This PR shares passive card text/pill presentation; overview primary/reclaim/recovery controls and authoritative attention reason are explicitly left under boxes 4/5 for the next slice.
    5. Preserve existing project actions when reducing height.

      • Files/functions: GraphCanvas.zig attentionActionBounds, reclaimOfferBounds, their hit tests, and drawNode; out-of-lease request: App.zig only the project-card portion of syncAccessibility and corresponding hidden-native tests.
      • Change: place existing Reply/Inspect and Reclaim/Keep in the card's detail row above metadata, not at the old bottom-right/bottom-left overlay positions. Paint, pointer hit-testing and UIA use exactly the same rectangles and the same visibility decision. Replace syncAccessibility's bounds.right-20,bounds.bottom-12 probe with direct per-node presentation/visibility lookup; the old point is no longer inside the moved action. Preserve existing action identities/invoke routing and safety checks. Do not wire new commands, change attention-rollup membership, or edit other App surfaces.
      • Test root GraphCanvas.zig, loop card parity actions never cover meta or each other: attention, reclaim, keep and long detail cases across zooms; shared hit targets inside the card and strictly above meta; no phantom hit target where the action isn't painted. Expected baseline RED: attention bottom-28..bottom-8 and reclaim bottom-24..bottom-5 overlap the new bottom meta row and reclaim uses unscaled fixed insets.
      • Test root App.zig, loop card parity UIA actions share painted logical bounds and scale once: use the existing never-shown overviewTestApp/OverviewAccessibilitySink pattern (19791-19820), not RealShellWindow/full App suite. Seed named attention and safe reclaim fixtures; publish UIA data at 96/120/144/192 DPI and pan/zoom; assert exact identities/count >0, one physical conversion and the same per-node visibility/rect as paint/hit-test. Preserve negative cases for nonattention, kept or unsafe worktrees. Expected baseline RED is overlap/geometry and stale point-based publication when testing the compact layout, not inability to create a window.
      • Existing action invocation/safety regressions run in exact-head CI. A hidden UIA-data sink is not live COM invocation, physical input or proof a terminal opened.
    6. Evidence and directly related ledger wording.

      • Out-of-lease request: a narrowly scoped edit to investigation/ui-parity-matrix.md, Loop card live detail row only, to remove the obsolete tokens/model-tier card requirement and describe actual slice evidence/limits; keep status Partial. Do not mark neighboring rows validated.
      • No historical baseline edit; no live-gate edit; no test-runner edit because GraphCanvas/AppFont/App roots are already registered (Tools/windows/Tests/WindowsShell.Tests.ps1:1032,1150,1214).

    File scope for approval

    File Lease/status Intended change
    graphcode-windows/src/GraphCanvas.zig B-owned Shared compact card body, bounded geometry, owned presentation/elision, fonts/pills, overview card pitch/extents, existing action rectangles and focused tests. Preserve #789 detail-panel helpers.
    graphcode-windows/src/DesignTokens.zig B-owned loop_card_height 106 -> 96, plus only card-local named metrics/colors actually needed. No sidebar/header/terminal/grid/zoom metric sweep.
    graphcode-windows/src/AppFont.zig Wave 1 single-cache exception supplied in kickoff Extend existing cache key/style access; preserve old APIs; focused face/weight/growth tests.
    graphcode-windows/src/App.zig OUTSIDE LEASE: approval required before implementation syncAccessibility project-card action visibility/rectangle block and one never-shown focused regression. No layout, menu, workspace, startup, add-control or decoder changes.
    investigation/ui-parity-matrix.md OUTSIDE LEASE: approval required Only card-live-detail wording/evidence correction, still Partial.

    No changes to GraphModel, shared Swift, provider pins, the historical manifest, Sidebar, TerminalSurface, JumpPalette, NativeForms or the gate/runner scripts. Those broader needs are documented above for later slices, not permission to edit them now.

    Risks and constraints

    • Shared App lease: lanes C/D/E also change App.zig. The coordinator must approve the named UIA block/test and avoid concurrent overlap. AppFont affects many native controls: old API defaults and font ownership must remain stable.
    • Existing neighboring PRs: at planning time the open-PR query returned Bound UIA Worktrees gate traversal #587 (gate and WindowsShell contract changes), Show loop-to-loop discussion on the board (#273) #274, Rename a loop when its session is renamed (#261) #263, An earned, one-time in-app ask to star the repository #207 and Build GraphcodeKit, the CLI, and graphcoded on Linux (#83) #110; no open wave-1 canvas PR. Do not edit the gate alongside Bound UIA Worktrees gate traversal #587. Recheck this list and rebase at implementation start. Read the entire git diff origin/main...HEAD after every rebase, especially preserving merged Windows: make the right loop panel readable and match its macOS hierarchy #789's text bounds/detail-panel geometry rather than restoring the abandoned card branch wholesale.
    • DPI/theme: main canvas paints through an anisotropic logical map (App.zig:9115-9127); do not combine logical zoom with a second selectForDpi scale. Tests cover 96/120/144/192 DPI, but cannot claim physical mixed-DPI hardware. Use COLORREF 0x00BBGGRR; don't silently update Theme/historical manifest to make a contract pass.
    • Card fonts/resources: finite zoom quantization can still create many distinct HFONT keys; test growth under a populated process-global cache. Never evict a native-control-borrowed font, double-delete a selected object, or return a short-lived UTF-8 slice backed by a local array.
    • Overview blast radius: 250-wide cards need wider spacing and accurate lane/content extents; update both geometry and consumers. Keep project/node stable identity and worktree caption actions; no new topology algorithm in this PR.
    • Advanced macOS fields: Node lacks richer summaries/resolution/exit-code/dependent data. Match only reported fields; don't convert build success into complete state/presentation parity.
    • Gate/harness: no edits planned. If tests or screenshot tooling need one, ask for exact file/function approval and add positive/negative ValidationRunner.Tests.ps1 cases before use.
    • Environment: use implementer's own C:\gcs\ID, C:\gct\ID, C:\gcp\ID; real validation folder C:\gcID, total length <=7. Never use pb's or another worker's cache. Seed only from read-only C:\gc-cache, bootstrap pinned environment, load it in each shell; process-only TEMP/TMP/profile. Seed onboarding-seen in fixture GRAPHCODE_SUPPORT_DIR. No stale real-profile zmx log use, junction validation roots, PATH Swift/Zig substitutions or full local desktop suites.

    Exact evidence plan

    I opened the following existing images directly: macOS 727-canvas-final-states@1x-srgb.png, 740-parityref-above-threshold-8-linked-canvas@1x-srgb.png, 740-global-graph-both-lanes@1x-srgb.png, final RefMain crop and needsyou-idle RefGoal crop; Windows beta26 tg727-728-canvas.png and tg720-project-canvas-1440.png. The images show the final 727 canvas at 68%, the 740 project/global canvases at 80%. They are not 100%-zoom card-size measurements. The image showing NEEDS YOU with no button on RefGoal also proves not to infer action availability just from a pill/crop; test source-driven states explicitly.

    Windows fixture/surface to capture once on final head, before and after Mac pair What can be decided
    Project canvas at actual 100% zoom/96 DPI; named wired Main IDLE, Goal DONE, Turn STOPPED, Timed SCHEDULED; normal and long titles, non-default Copilot CLI and default Claude; isolated card crops alongside full viewport 727/727-canvas-final-states@1x-srgb.png plus 727-final-RefMain, RefGoal, RefTimed, RefTurn crops Title/state/meta hierarchy, source-verified 250x96 logical size, mono rows, backend display names, containment. Preserve originals and label mac's 68% zoom; a normalized crop is derived evidence, not a newly captured mac100 state.
    Project canvas question fixtures: stored running+awaitingInput, plus stored succeeded/blocked+awaitingInput; safe resolved worktree Reclaim/Keep; long slash branch and activity, both positive/negative metric direction 740/740-parityref-above-threshold-8-linked-canvas@1x-srgb.png and 727/727-canvas-needsyou-idle-state@1x-srgb.png Pill hierarchy; existing project action/meta separation. Do not claim the remaining no-glow/rollup/action gaps are fixed. Metric/long-text cases are regression fixtures absent from the mac bundle, paired to source and explicitly labeled, not invented runtime equivalence.
    Global overview containing the same ordinary loops in two named project lanes, plus four-loop lane to exercise next row and second lane 740/740-global-graph-both-lanes@1x-srgb.png Both canvas renderers use the same card hierarchy and geometry; lane/card spacing does not regress. The mac pair has a populated ParityRef lane and an empty ParityRef2 lane; the second populated Windows lane is additional regression evidence, not identical mac fixture coverage. Topology/palette/caption differences remain declared.
    Minimum/maximum canvas zoom and 150%/200% DPI memory-DIB geometry/font/action tests; no additional desktop slot Above pairs only for hierarchy, not hardware proof Positive in-bounds geometry and once-only scaling. RUNNING/BLOCKED real mac captures are unavailable per README; fixture tests/source cannot be called paired runtime proof.

    Fixtures must be privacy-safe, offline/stub-backed and require no paid backend turn. Use stable node/project IDs, named rows rather than broad "all cards" counts, fixed now for unit expectations and recorded capture clock for ages. No private bundle ZIPs or the forbidden needs-you-click capture.

    Execution sequence for implementer:

    1. Seed/verify pinned tools/providers under the implementer's own roots and run Tools\windows\bootstrap.ps1 as specified by the kickoff. Redirect process TEMP/TMP; use each environment.ps1.

    2. From graphcode-windows, run the registered roots with positive-count checks and logs kept separately for RED/GREEN:

      & $env:GRAPHCODE_ZIG0152 test src\GraphCanvas.zig -target x86_64-windows-msvc -lc -luser32 -lgdi32 "-I$env:GRAPHCODE_WINGHOSTTY_ROOT\include" --cache-dir "$env:TEMP\card-local" --global-cache-dir "$env:TEMP\card-global" --test-filter "loop card parity"
      & $env:GRAPHCODE_ZIG0152 test src\AppFont.zig -target x86_64-windows-msvc -lc -luser32 -lgdi32 "-I$env:GRAPHCODE_WINGHOSTTY_ROOT\include" --cache-dir "$env:TEMP\font-local" --global-cache-dir "$env:TEMP\font-global"

      GraphCanvas existing regression subset: select the changed card/overview geometry and Windows: make the right loop panel readable and match its macOS hierarchy #789 text-bound tests in focused runs; never report a zero-match filter as pass. App native tests need the complete native/provider library link arguments from WindowsShell.Tests.ps1:1214-1217, with --test-filter "loop card parity" and owned caches appended; do not run unfiltered App locally. Build missing provider artifacts only through the pinned repository build route.

    3. From repo root, run pwsh -NoProfile -File Tools\windows\validate.ps1 -Task visual-baseline with the owned environment/scratch settings. This is source/fixture validation, not proof of rendered card parity. Leave the historical 250x106 token untouched.

    4. Publish draft PR, let exact-head CI run all unit shards, integration and packaging; count nonzero relevant jobs and cite run/job IDs. No local full unit/integration suite. Complete the required different-vendor code review, with actual log opens and complete rebase-diff inspection.

    5. Only after code review plus exact-head green: request one <=15-minute desktop lease. Capture main-before and final-head-after at 100% using PrintWindow(hwnd,hdc,2) and pixel verification. Keep full client geometry, DPI/zoom and source SHA with each image. No ActivateWindow/Alt-tap workaround. Exit via File > Exit; stop/verify only owned process IDs including zmx; release lease.

    6. Upload only safe fixtures and have the reviewer open every mac/before/after image, with a table stating which box each proves or leaves partial. Missing real mac100 or RUNNING/BLOCKED evidence stays explicitly unverified; a source-normalized image cannot silently become a native capture.

    7. Fill the repository PR template with honest RED/GREEN/REGRESSION commands/results and Not verified list; Refs #764, no Fixes. Validate body, make ready, wait for exact-head checks again, hand off without merging.

    Planner evidence limit: source/issue/ledger/capture investigation only. Proposed RED results are predictions grounded in the named main implementations, not preserved failing test logs. Implementation must obtain and retain its own compiling behavioral RED before making the fix.

  6. coneilen commented on Oct 14, 2026

    @coneilen
    CollaboratorAuthor

    Plan approved (coordinator)

    Approved: #764 (comment), compact passive loop-card presentation in project and overview only, with the existing project actions kept functional when the card shrinks. Boxes 2-8 remain open; box 1 is not checked until the final evidence actually supports it, and missing decoded state information remains an explicit limit.

    Approved scope: graphcode-windows/src/GraphCanvas.zig (card body/layout, overview pitch/extents, owned text/elision, existing card action rectangles/tests; preserve #789 helpers); graphcode-windows/src/DesignTokens.zig (card-local metrics/colors only); graphcode-windows/src/AppFont.zig (the existing single cache, style key/accessors and growth tests, preserving every old API/default/handle lifetime); graphcode-windows/src/App.zig only the project-card action visibility/rectangles inside syncAccessibilityTo and the named never-shown regression. This is explicit approval of that out-of-lease App block.

    Not approved: investigation/ui-parity-matrix.md. The coordinator owns the post-merge live-detail wording correction and will remove tokens/model tier without changing workspace-detail availability. No manifest, GraphModel, gate/runner, Sidebar, TerminalSurface, JumpPalette, NativeForms, topology, type-palette sweep, new overview actions or add-button changes. Leave historical manifest 250x106 untouched.

    I verified the relied-on macOS claims: LoopCardView.swift:46-52,134-163,265-297 (250x96, fonts, title/pill, meta square and role chips); LoopCardPresentation.swift:108-141,155-180,185-241,272-281 (activity priority/pass prefix, detail/metric, ordered meta omitting default Claude Code and tokens/tier, duration); LoopStateAppearance.swift:31-53,74-121 (all words including timed SCHEDULED and hollow indicators); LoopTypeAppearance.swift:43-47 and BackendCapabilities.swift:70-79 (type/backend names). CanvasBand.swift:129-181 confirms ENTRY still exists; do not infer its removal from comments in LoopCardView. I opened the Run A final card canvas and observed its 68-percent zoom; screenshots are not intrinsic 100-percent size evidence.

    Tests must first compile against existing production entry points or behavior-preserving seams and fail literal 250x96/project-and-overview, row containment, current metadata/activity, or actual font/paint assertions. Missing style/presentation helpers are not RED. Exercise both render consumers, title/pill allocation, complete long Unicode/slash-branch source strings, measured elision preserving backend/age, allocation cleanup, named action bounds/hits/UIA and positive executed counts. Preserve failed logs. Font-cache tests assert bounded growth from a populated starting point and zero second-sweep growth, never an absolute global count. No success-shaped truncated-string fallback or per-paint leaked cache-overflow font is permitted.

    App coordination: C owns sidebar publication, D workspace publication, E context-menu construction. Your lease is the project-card block only; no edits to those neighboring branches. Rebase before each push and inspect every hunk. Run visual-baseline locally, exact-head CI for full regression, then accepted cross-vendor code review before one <=15-minute screenshot lease. Use Refs #764; do not promote any ledger row or claim full attention/state parity from a pill or hidden sink. Worker ib owns C:\gcs\ib, C:\gct\ib, C:\gcp\ib and real C:\gcib.

    Implementation model (user-directed policy, 2026-10-14): gpt-6.1-sol, high, long_context. The actual plan has six interdependent steps across large GraphCanvas/App code: unify two different renderers, extend the process-global borrowed-font cache safely, introduce allocation-safe measured Unicode text/elision and state pills, and realign paint/hit/UIA action geometry. This is substantially more than a local card-layout fix and meets the many-interdependent-steps/shared-mechanism exception. No further scope expansion is implied. Reviewer remains claude-sonnet-5.5, high.

    Launch hold (new user-directed planner-rating step): the approval/model assessment above was posted before this additional policy arrived. Scope/source review is complete, but effective model approval and implementation launch are pending the original planner's own standard/highly-complex rating and reason in its plan comment. No implementation session for this lane has launched.

    Final model/launch approval: the original planner has now added highly complex with an explicit coupled-renderer/font-cache/owned-Unicode/paint-hit-UIA rationale to the plan comment. I read and accept that assessment for the approved compact-card scope. The launch hold is lifted: use gpt-6.1-sol, high, long_context, reviewer claude-sonnet-5.5, high. The denied ledger edit remains denied.

    Narrow test-only scope extension (ib request): after reading both existing tests on main, approve only project/overview card rectangle and consequent overview lane/pitch literal updates in App.zig tests DPI UIA logical cards headers sidebar and direct inserts scale once and cross-project overview refresh emits both projects without changing the selected loop. Current literals encode 250x106 project and 220x86 overview cards, so preserving them would reject the intentionally approved 250x96 shared card. Keep identity/selected/card-count/refresh semantics and unrelated header/sidebar/workspace/chat expectations unchanged. Expected coordinates must remain independently calculated literals with once-only DPI conversion; do not derive the oracle from the production layout helper. No extra production scope is granted.

    Additional test-only scope extension (ib): approve only beta_bounds literals in the existing App.zig test 'cross-project overview Open actions select exact projects and emit DPI scoped cards'. Read on main: old 96/144/192-DPI rectangles encode 220x86. The approved 250x96 bounds are {262,442,512,538}, {393,663,768,807}, {524,884,1024,1076}. Preserve the origin, six-card count, owner/row identities, selected flags, Open-action coordinates/outcomes and every other assertion. No additional production scope.

    Coupled review/CI scope correction: approve overview pitch 260, minimum lane width 806 and content width 854, retaining 250x96 cards, positive 10px gap, row-major identities/order and consistent extents. Only the named existing App geometry literal tests may change consequently. Verify real narrow (~1028 client)/wide, zoom/pan, nonoverlap and content/action reachability; an in-client centre alone is not proof a whole button is visible. Preserve normal clipping/panning, no sticky caption or desktop widening.

    Explicit new harness lease: uia-live-gate.ps1 Test-MultiProjectObservedRoster scale/height, Get-MultiProjectLaneGeometry scale/height/minimum, early lane Open action minimum and directly related explanatory comments (old 5994/6003/6034 sites); final independent oracle values 250/96/806. ValidationRunner.Tests.ps1 matching executable positive/negative narrow/wide geometry/roster cases, rejecting old 220x86/760 assumptions. Preserve all exact named rosters, owner/action identity and outcome assertions. Run the script locally. A prompt helpers, C sidebar oracle and E menu/connector oracle are disjoint and must survive rebase.

    Metadata overflow policy: isolate metadata failure from title/pill/live/detail painting. Prefer measured head/middle-ellipsis/tail; if that minimum cannot fit, use one measured branch ellipsis only when it plus its extra separator fits. If no branch fragment fits but fixed type/backend/age do, omit only branch and its separator as explicitly tested/documented visual elision while retaining complete owned source. If fixed fields do not fit, log the specific error and draw a bounded metadata-unavailable marker only, not a blank card or clipped backend/age. Test exact width boundaries and the reported Composite/Copilot CLI/1h5m/main case, Unicode/surrogate cuts, allocation failures and action paint. Existing subgraphNodeCount reuse and honest build-log provenance correction stay in scope.

    Reviewed actual integration failure 38592395839/job115950535486: early lane Open target used the stale floor and failed before later ratio checks. One change-caused CI failure and one substantive reviewer rejection (two findings), corrected in place; no escalation.

    Additional exact early Worktrees coordinate lease: approve the old6068 laneWorktreesScreenX assignment from freshly read graph bounds using Left+24+Max(806,Width-48)-45, not Right-69 (which assumes the non-minimum branch). Matching ValidationRunner cases must extract/evaluate the actual production assignment AST for narrow and wide bounds, reject old959 as the Open target in the1028-client case, and validate the1005 Worktrees point against actual/visible control geometry. Keep all outcome/ownership/count/clip checks and actual-size assumptions; no desktop widening or broad coordinate replacement. Audit remaining overview caption/card consumers for stale220x86/760/right-edge assumptions and document untouched valid sites before final review. This conditional source finding is not an additional CI failure without an actual failed run.

  7. coneilen commented on Oct 15, 2026

    @coneilen
    CollaboratorAuthor

    Merged the compact-card code slice in #814 as 4fafbfb after accepted code/narrow interaction review, current exact-head CI and fresh ready-event DCO. The operator explicitly moved deferred-visual merge authorization to now because #826 blocks captures. Visual verification is NotExecuted for that reason; no AFTER capture, paired-image acceptance or visual-parity claim is made. All acceptance boxes remain unchecked and #764 stays open. The original inherited journal sharing-violation CI failure and one successful targeted same-head rerun are retained in the PR. This merge is not current binary/capture admission: the old fresh a44/base65194 builds are historical only. Visuals will be reviewed in a later coordinated batch once the tooling is qualified; no ledger row is promoted by this merge.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1 layoutParity priority 1: layout, clipping, overlap, broken rendering vs macOSenhancementNew feature or requestparityWindows/macOS UI parity ledger row (investigation/ui-parity-matrix.md)visual parityVisual fidelity against the macOS reference captureswindowsWindows port

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions