Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ jobs:
- name: Prepare ACP adapters
run: pnpm --filter lody prepare:acp-adapters

- name: Install geometry test browser
run: pnpm --filter @lody/components exec playwright install --with-deps chromium

- name: Run tests
env:
GIT_CONFIG_COUNT: 1
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@
"format:check": "corepack pnpm -r --filter '!acp-extension-claude' --filter '!acp-extension-codex' run format:check",
"typecheck": "corepack pnpm --filter lody prepare:acp-adapters && corepack pnpm -r --workspace-concurrency=1 --filter '!acp-extension-claude' --filter '!acp-extension-codex' run typecheck",
"test": "corepack pnpm -r --workspace-concurrency=1 run test",
"test:ci": "corepack pnpm -r --workspace-concurrency=2 --filter \"!@lody/electron\" --filter \"!acp-extension-codex\" --filter \"!acp-extension-claude\" run test --maxWorkers=2 && corepack pnpm --filter @lody/electron run test",
"test:ci": "corepack pnpm -r --workspace-concurrency=2 --filter \"!@lody/electron\" --filter \"!acp-extension-codex\" --filter \"!acp-extension-claude\" run test --maxWorkers=2 && corepack pnpm --filter @lody/electron run test && corepack pnpm test:geometry",
"test:geometry": "corepack pnpm --filter @lody/components test:geometry",
"geometry:triage": "node packages/components/scripts/triage-geometry-findings.mjs",
"test:watch": "corepack pnpm -r run test:watch",
"test:coverage": "corepack pnpm -r run test:coverage",
"lint:fast": "oxlint --quiet --ignore-pattern packages/acp-extension-kimi",
Expand Down
1 change: 1 addition & 0 deletions packages/components/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ test-results/
playwright-report/
playwright/.cache/
geometry-report/
geometry-report-next/
82 changes: 4 additions & 78 deletions packages/components/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,84 +14,10 @@ mobile surfaces.
- Compact number units (K/M/B vs 万/亿) follow the product language via
`toIntlLocaleOrEn` / `formatCompactNumber`, never the host OS locale.
- Prefer shared primitives from `src/components/ui` over private replacements.
- `src/lib/chat-workspace-geometry.ts` owns the bootstrap mathematical design grid
for the authenticated Web chat workspace. Its current numeric grid is a reviewed
reference, not evidence that the product was automatically inferred. Production
layout stays ordinary Flex/Grid and exposes only stable geometry data markers;
never turn its columns into
component props or wrapper DOM. The Storybook fixture and Playwright gate
consume the same spec. In development, `?geometry=1` adds the reference overlay
for both the Sidebar-local and Main Pane grids, semantic alignment lines,
spacing-rhythm diagnostics; none mounts in production or tests.
Semantic alignment compares explicit control boxes: repeated slots may share
an X-axis line across rows, while icon/text controls within one row may share a
Y-axis instance. Cross-font rows use visual ink centers: text comes from the
rendered font's actual ascent/descent metrics, SVGs from their transformed path
bounds, and CSS shapes from their visible boxes. Typographic baselines compare
text only; glyph weight and perceived balance remain CV concerns. Named groups
expose stable member labels; diagnostics place guides at the median and assess the
complete member spread, never DOM order. A measurable spread above tolerance but at
most 1px is `sub-pixel-jitter`, stays folded in reports, and never enters the gate;
larger spreads are violations. Groups below their required member count are
`insufficient-evidence`, never aligned. Padding, margin, gap, and line-height multiples
are spacing diagnostics, never alignment violations. Diagnostic debt remains
non-blocking until a rule is explicitly promoted into the Playwright gate.
Alignment-rail discovery is an earlier, heuristic stage. It mines repeated sibling
subtrees from DOM topology and geometry; each repeated subtree instance contributes
at most one member to a start/center/end rail, so a control and its nested icon cannot
manufacture support. `data-geometry-discovery-scope` remains an optional hint for
named or aggregate regions, never a prerequisite for discovery. Coordinate modes with
repeated row support establish rails independent of DOM order before nearby singleton
observations attach to the nearest mode; intermediate coordinates must not chain distinct
indentation levels together. Discovery uses final rendered coordinates, not component
structure or row-container boundaries, to infer multiple stable visual rails. Every candidate
remains eligible for its nearest rail across the full scope; broad scans deliberately prefer
an extra review candidate over silently excusing a shifted module or indentation level. Two
repeated visible rows are sufficient to establish a local indentation rail; do not absorb a
legitimate two-row visual level into a nearby rail merely because a broader rail has more support.
A rail established by one rendered primitive kind accepts only that kind; cross-kind attachment
requires mixed support on the rail itself so a coincidentally nearby icon is not judged by a text rail.
Coordinate peaks merge only within the inlier tolerance. Two repeated peaks one CSS pixel apart are
distinct visual levels; do not median them into one rail and report the smaller peak as misaligned.
Members outside the inlier tolerance are reported as outliers, and confidence includes the
rail's span relative to its containing scope rather than the minimum-span admission threshold. Flow
text may contribute start/end edges but never a center rail because its box center changes
with content and wrapping; center evidence comes from controls with an explicit geometry.
Numeric text, including signed diff statistics, canonically uses its trailing edge so changing
digit counts do not manufacture leading-edge outliers in right-aligned columns.
Alignment discovery measures visible primitives in ink space: text through rendered `Range`
bounds, SVGs through transformed path bounds, and images through their painted boxes. A padded
control or container is a separate layout-box observation and must never cluster with an ink
rail; spacing diagnostics own padding, margin, border, and gap measurements.
Discovery must not read semantic-alignment attributes: it derives visual rows and their
direct layout slots from ordinary DOM topology, including transparent hover controls that
still occupy layout. It preserves each slot's start/center/end family until all captures
choose one canonical anchor together. Contract inference samples every workspace
verification viewport, caps normalized merging at 4 physical pixels, and counts missing
scope observations in capture coverage. Rails with the same topology signature and
normalized position across captures may become evidence-backed contract proposals. A
discovered rail or proposal is not layout intent and cannot pass or fail the gate until
a contract compiler binds it to stable
semantic members and review promotes it into a named rule. Geometry-report capture covers
the workspace Sidebar, the production-composed session right sidebar, and session states that
materially change visible geometry; it does not duplicate an isomorphic conversation layout
solely for a transient interaction such as mention drop. Each report detail persists the
capture id whose coverage entry owns its Story, viewport, and device scale. `--after` must
replay that original capture and clip, then append only the repair image; never rediscover
findings or replace the before/guide evidence, because a successful fix may remove the original
candidate. The fixed HTML renderer parses one embedded `application/json` payload, while every
screenshot remains a referenced file rather than Base64 data.
Violation images label every deviating member in place with its human-readable role, physical
direction, measured offset, actual anchor, and a leader to the rendered element; a bare
internal member id or an unlabelled shared line is not an actionable design finding.
Discovery cards use product-region names rather than scope ids, count unique rendered
elements rather than repeated anchor votes, and group one element's start/center/end
offsets into one in-image annotation. Candidate rails stay visually subordinate to
emphasized outliers because a heuristic proposal is not yet a violation. Report capture
mode disables hover interaction and transitions, preserves explicit hover-action/rest
swaps, and automatically reveals transparent containers that own interactive controls.
Every measured control must therefore remain visible in both clean and annotated images
without requiring business components to carry geometry-only markers.
- The geometry constraint system measures rendered layout, turns it into reviewable findings,
and gates only what a human promoted. Invariants: [tests/e2e/AGENTS.md](tests/e2e/AGENTS.md)
(pipeline, identity, contracts, gate, report), [src/lib/AGENTS.md](src/lib/AGENTS.md) (grid).
Review state: `geometry-ledger.json`, `geometry-contracts.json`. Commands: `pnpm --filter @lody/components geometry:report [dir]`, root `pnpm geometry:triage <dir>`.
- `ui/emoji-picker.tsx` is the shadcn `frimousse` registry component, with its
two copy strings on i18n rather than the registry's inline English. Its dataset
SHIPS WITH THE APP: `frimousse` otherwise fetches
Expand Down
70 changes: 70 additions & 0 deletions packages/components/geometry-contracts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
{
"version": 1,
"tokens": {
"sidebar.trailingInset": {
"unit": "px",
"cssVariable": "--spacing-sidebar-trailing",
"expected": 9
}
},
"contracts": [
{
"name": "workspace.sidebar.primary-trailing-actions",
"story": "geometry-chatworkspace--expanded-sidebar",
"members": [
{
"role": "button",
"name": "New session"
},
{
"role": "button",
"name": "Remove project"
},
{
"role": "button",
"name": "Archive",
"rowFamily": "div[button]>div[text]",
"all": true
}
],
"axis": "x",
"anchor": "inline-end",
"space": "layout-box",
"tolerance": 1,
"findingKey": "geometry/workspace/lvwy4w"
},
{
"name": "workspace.sidebar.trailing-inset",
"story": "geometry-chatworkspace--expanded-sidebar",
"members": [
{
"role": "text",
"selfFamily": "div[text]>div[button],div[text]"
},
{
"role": "text",
"selfFamily": "div[text]>div[button],button[button]"
},
{
"role": "button",
"rowFamily": "div[text]>div[button]",
"roleIndex": 0,
"all": true
}
],
"axis": "x",
"anchor": "inline-end",
"space": "layout-box",
"tolerance": 0,
"relation": {
"kind": "box-model-sum-equals-token",
"properties": [
"padding-inline-end",
"border-inline-end-width"
],
"token": "sidebar.trailingInset"
},
"findingKey": "geometry/workspace/sidebar-trailing-inset"
}
]
}
Loading
Loading