diff --git a/.gitignore b/.gitignore index 06957a69..678a523c 100644 --- a/.gitignore +++ b/.gitignore @@ -28,9 +28,12 @@ Thumbs.db # Claude / Superpowers workspace artifacts .superpowers/ .claude/ -docs/superpowers/ # Backups dropped by /caveman:compress next to compressed files *.original.md +# Docs — ignore everything except per-node help pages +/docs/* +!/docs/help/ + # Semantic release .releaserc.local.js diff --git a/docs/design-handoff/audit-2026-04-27.md b/docs/design-handoff/audit-2026-04-27.md deleted file mode 100644 index e6005d1f..00000000 --- a/docs/design-handoff/audit-2026-04-27.md +++ /dev/null @@ -1,331 +0,0 @@ -# UI Consistency Audit — 2026-04-27 - -Scope: every Vue file under `src/manager/` (47 files: views/, components/, layout/, community/). Read-only audit. - -## Summary -- Total findings: 48 (P0: 8, P1: 22, P2: 18) -- Highest-impact category: **massive duplication of `wp-tag-chip`, `wp-row-fav-btn`, `wp-row-name`, `wp-id`, `category-chip`, `wp-row-expand__title`, `wp-filters-grid`, `wp-fav-toggle`, `wp-field__label` rules across all 6 list views** (~150 lines of identical CSS in 6 files = ~900 LOC of redundancy). -- Quick-win count: ~22 (most are 1-line text-shadow / hex / token-fallback fixes; the bulk-fix is one extraction to tokens.css). - ---- - -## P0 — Visual breakage / inconsistency users notice - -- [ ] **Token name drift: `--wp-bg2/--wp-bg3/--wp-text2/--wp-text3` vs `--wp-bg-2/--wp-bg-3/--wp-text-muted/--wp-text-dim`** - - Files: `components/PipelineSteps.vue:218,222,230,243,256,264,278,284,292,309,312,319,321,336,341,360,366,386,396,398`, `components/PipelineStepPicker.vue:218,228,240,246,271,275,289,303,305,316,325,354,364,372,383,389,390,417,434,435,440`, `components/HistoryPanel.vue:179,190,208,229,233,257,261,271`, `components/DerivationRuleCard.vue:331,342,355,403`, `views/PipelineEditor.vue:385` - - Both spellings work (tokens.css 2239–2245 aliases them) but the unprefixed `bg2/bg3` form is the non-canonical legacy name. Mixing within one component (e.g. `PipelineSteps.vue` uses ONLY `bg2/bg3/text2/text3`) is harmless but confusing. - - Fix: pick one form (`--wp-bg-2/--wp-text-muted` is canonical per tokens.css line 35–46) and replace all `bg2/bg3/text2/text3` references. - - Effort: M - -- [ ] **`category-chip` rule duplicated 8 times verbatim** (incl. `text-shadow: 0 0 2px rgba(0,0,0,.4)`) - - Files: `views/Wildcards.vue:427-435`, `views/FixedValues.vue:289-297`, `views/Combines.vue:320-328`, `views/Constraints.vue:352-357`, `views/Derivations.vue:323-331`, `views/Pipelines.vue:382-387`, `views/Categories.vue:291-299`, `views/Dashboard.vue:327-336` (renamed `dashboard__row-chip`). - - Fix: extract `.wp-cat-chip` into `tokens.css` and remove all 8 copies. - - Effort: S - -- [ ] **List-view scoped styles are 95% identical across all 6 list screens** - - Files (approx 60 lines each duplicated): `views/Wildcards.vue:343-425`, `views/FixedValues.vue:228-308`, `views/Combines.vue:255-318`, `views/Derivations.vue:264-340`, `views/Constraints.vue:300-358`, `views/Pipelines.vue:336-388`. Rules: `wp-filters-grid`, `wp-field--full`, `wp-field__label`, `wp-fav-toggle`, `wp-tags-row`, `wp-tags-empty`, `wp-tag-chip`, `wp-row-fav-btn`, `wp-row-name`, `wp-row-name__text`, `wp-id`, `wp-row-expand__title`, `wp-row-expand__name`. - - `wp-row-fav-btn` is **already defined** by the parent `ModuleListView.vue:711-722`, so every list-view re-declaration is dead-effect duplicate. - - Fix: lift the shared `wp-filters-grid`, `wp-field__label`, `wp-fav-toggle`, `wp-tags-row`, `wp-tag-chip`, `wp-row-name`, `wp-id`, `wp-row-expand__title` into `tokens.css` and delete from each list view. - - Effort: M - -- [ ] **Broken click handler in `ModuleListView.vue` default favorite slot** - - File: `components/ModuleListView.vue:480` — `@click.stop="$emit && $emit"` is a no-op string (not a method call). Falls through to nothing — the default `` button does nothing when used without a parent override (every list view overrides it, so this masks the bug). - - Fix: either remove the default slot's button entirely, or wire it to a real `(emit:fav, row)` callback. - - Effort: S - -- [ ] **Card subtitle is rendered after the `wp-spacer` so it sits on the right** - - File: `components/ui/Card.vue:17-20` — `

{{ subtitle }}

` lives between title and ``. Since the header is `display:flex` (per tokens.css `.wp-card__header`), subtitle renders inline next to title, then the spacer pushes actions to the right. There's no `.wp-card__subtitle` rule in `tokens.css` either, so it inherits `

` defaults. - - Fix: wrap title+subtitle in a `wp-card__title-wrap` div and add `wp-card__subtitle` rule (font-size 11.5px, color text-dim, margin 0). - - Effort: S - -- [ ] **`Settings.vue` and `TweaksPanel.vue` Buttons use `icon="pi-refresh"` (no `pi ` prefix)**, while 37 other Buttons use `icon="pi pi-refresh"` - - Files using bare form: `views/Settings.vue:99`, `views/Categories.vue:152,238`, `views/Dashboard.vue:158,159,191,192,193`, `views/TestRunner.vue:433,459,495,512,543,590,591,592,642,643,644`, `views/ImportExport.vue:537,542,547,554,645,801,809`, `components/TweaksPanel.vue:113`, `layout/AppTopbar.vue:175`. Files using prefixed form: 15 files (Wildcards, Combines, Constraints, Derivations, FixedValues, Pipelines, *Editor, EditorFrame, IdentityCard, ModuleListView, HistoryPanel, DerivationRuleCard). - - Both work because `Icon.vue:12` strips the redundant `pi` prefix. But 6 of 6 module list screens use one form, and most Dashboard/TestRunner/Settings use the other. - - Fix: pick one (recommend bare `pi-foo` since `Icon.vue` is the convention) and codemod-rewrite all 37 instances. - - Effort: S - -- [ ] **Heavy inline `style="..."` attribute clusters in community screens** - - Files: `views/community/CommunityProfile.vue:76,77,78,86,156,157` (7 instances incl. layout `display:flex; gap:8px`, `flex:1`), `views/community/CommunityDetail.vue:157,241,292,296,300,323` (6 instances with 50+ char layout strings), `views/community/CommunityUpload.vue:291,307,310`, `views/community/CommunityProfile.vue:80-83` (`

` blocks with no class). Stark contrast with main app screens that use scoped classes. - - Fix: extract to scoped classes in `community.css` (e.g. `.wp-comm-profile-meta`, `.wp-comm-profile-actions`, `.wp-comm-rating-row`). - - Effort: M - -- [ ] **Two parallel empty-state shapes: `.wp-empty` (lists) vs `.wp-comm-empty` (community)** - - `wp-empty`: `components/ModuleListView.vue:521-528,755-775`, `views/Categories.vue:173-176`. Centered icon disc + msg + CTA. - - `wp-comm-empty`: `community.css` (imported in 6 community screens). Same shape, different CSS. - - Fix: consolidate on one (`wp-empty` is older/canonical) — rename `wp-comm-empty*` to `wp-empty*` and inherit; saves duplicated rules. - - Effort: M - ---- - -## P1 — Polish that elevates quality - -- [ ] **`opt-empty` rule duplicated** across editors - - `views/WildcardEditor.vue:377-381`, `views/FixedEditor.vue:271-275` — identical `text-align:center; padding:16px; color:var(--wp-text-dim)`. - - Fix: move to `tokens.css` as `.wp-table__empty-row`. - - Effort: S - -- [ ] **`wp-row-expand__title` rule duplicated 6 times** - - `views/Wildcards.vue:474-481`, `views/FixedValues.vue:299-306`, `views/Combines.vue:332-339`, `views/Constraints.vue:360-367`, `views/Derivations.vue:333-340`, `views/Pipelines.vue:398-405`. Identical font-size 10.5px / uppercase / 0.06em letterspacing. - - Fix: move to tokens.css. - - Effort: S - -- [ ] **`wp-tag-chip` rule duplicated 6 times** with subtle variants - - All 6 list views. `Wildcards.vue:372-392` adds `transition: all 0.12s` and a `:hover` rule; `Combines.vue:281-296` and `Constraints.vue:321-332` and `Derivations.vue:288-303` and `Pipelines.vue:353-364` and `FixedValues.vue:249-265` omit them. - - Fix: extract single canonical version with hover state. - - Effort: S - -- [ ] **Sidebar CSS (`AppSidebar.vue`) — only 1px raw value but no scoped style block reviewed**; visually fine but worth checking width breakpoints - - File: `layout/AppSidebar.vue:1` (count: 1 instance per the grep summary; minimal). - - Effort: S — skip unless visual issue reported. - -- [ ] **Tailwind-style utility classes leak into community screens** - - `views/community/CommunityDiscover.vue:235-238` defines `.w-full`, `.w-40`, `.w-44`, `.mt-3`. `Community404.vue:35`, `CommunityProfile.vue:175-176`, `CommunityOffline.vue:67-77` redefine `.mt-3` / `.self-start` per-file. - - Fix: pick token names (e.g. `wp-w-40`) or use inline `style="width:160px"`-free flex layouts. Prefer scoped class with project naming convention. - - Effort: M - -- [ ] **Hardcoded color hex outside `tokens.css`** - - `components/RichTextInput.vue:416-417` (`color:#34d399; background:color-mix(... #10b981 ...)`), `:422-423` (`color:#fb923c; background:color-mix(... #f97316 ...)`) - - `components/RichTextPreview.vue:66-67,73-74` (same) - - `views/ImportExport.vue:1005-1006` (`#fb923c`). - - These are token-violations: the green/orange semantic colors should come from `--wp-success`/some new `--wp-token-warn`, etc. - - Fix: introduce `--wp-rt-token-good`, `--wp-rt-token-mut`, etc. in tokens.css. - - Effort: S - -- [ ] **`wp-row-fav-btn` re-declared in 6 list-view scoped blocks — no-op** - - Already exists in `components/ModuleListView.vue:711-722`. Each list view inside a `` rebinds the class but the CSS is unscoped-correctly-only on the parent component due to Vue scoped CSS. Net effect: each list view's copy has the unique scope hash and DOES override — and they're identical. - - Fix: delete from all 6 list views. - - Effort: S - -- [ ] **`wp-id` rule duplicated 6 times** - - All 6 list-view scoped blocks. Identical. - - Fix: extract to tokens.css. - - Effort: S - -- [ ] **`wp-fav-toggle` rule duplicated 6 times** - - All 6 list views. - - Fix: extract to tokens.css. - - Effort: S - -- [ ] **`wp-filters-grid` + `wp-field--full` + `wp-field__label` duplicated 6 times** - - All 6 list views. - - Fix: extract to tokens.css. - - Effort: S - -- [ ] **Wildcard editor's Options card uses `:padding="false"` while no other editor does** - - File: `views/WildcardEditor.vue:283`. None of the other 5 editors use `:padding="false"` on their main table card. Wildcard editor uses native table padding to align with cells; others wrap tables inside the card body's 14px padding. - - Fix: standardize on `:padding="false"` for any Card containing a `` — apply to FixedEditor, ConstraintEditor exception table, DerivationEditor (no table). Or remove from Wildcard for parity. - - Effort: S - -- [ ] **`wp-text-xs` defined locally in WildcardEditor only** - - File: `views/WildcardEditor.vue:358` (`.wp-text-xs { font-size: 11.5px; }`). Other editors hardcode `font-size: 11.5px` literally (PipelineEditor `.pl-hint`, DerivationEditor `.dv-hint`, ConstraintEditor `.cn-hint`). - - Fix: use one shared `wp-text-xs` token (or the existing `wp-card__hint`). - - Effort: S - -- [ ] **Inconsistent button variants for "Open external link" / "Docs"** - - `views/Dashboard.vue:158` uses `variant="outline"` for Docs. - - `views/Settings.vue:65` Repo link uses raw `` tag, no Button. - - `layout/AppTopbar.vue:175` uses `icon="pi-github"` plain Button. - - Fix: pick one pattern for "external nav" (recommend Button variant ghost + `iconRight="pi-external-link"`). - - Effort: S - -- [ ] **`category-chip` border-radius differs** - - 6 list views: `border-radius: 9px`. `views/Dashboard.vue:331` for `dashboard__row-chip`: `border-radius: 9px` (matches). `views/ImportExport.vue:897` `wp-io-row__catchip`: `border-radius: 999px` (pill). Inconsistent shape for "category visual". - - Fix: pick one (9px is the prototype's intent per `docs/design-handoff/wildcardpipeline/project/`). - - Effort: S - -- [ ] **Editor scaffold sizes differ across 6 editors** - - `WildcardEditor 404 LOC`, `FixedEditor 276`, `CombineEditor 327`, `DerivationEditor 290`, `ConstraintEditor 452`, `PipelineEditor 410`. Variation expected, but Wildcard has unique `.wp-text-xs`, FixedEditor has `.fv-row__err`, ConstraintEditor `.cn-empty/.cn-pair/.cn-cross`, PipelineEditor `.wp-pl-flow*` etc — all are decent local styles but several rules (e.g. `.dv-empty` vs `.cn-empty`) recreate the dashed-empty-card pattern. - - Fix: introduce shared `wp-empty-card` (dashed border + center text + 16px padding) in tokens.css. - - Effort: M - -- [ ] **`opt-prob` width 32px hardcoded** in WildcardEditor - - File: `views/WildcardEditor.vue:399`. Probability bar fixed-width unit. - - Effort: S — token candidate. - -- [ ] **`HistoryPanel` width hardcoded 480px** - - File: `components/HistoryPanel.vue:164`. Modal-like drawer should use a token (`--wp-drawer-w` or `min(480px, 100vw)`). - - Effort: S - -- [ ] **`PipelineStepPicker` border-radius/padding mostly fine but mixes `var(--wp-radius-sm)` with literal `4px` and `5px` in same file** - - File: `components/PipelineStepPicker.vue:289` (`border-radius: 4px`), `:303` (`border-radius: 5px`), `:434` (`border-radius: var(--wp-radius)` ). - - Fix: standardize on tokens. - - Effort: S - -- [ ] **`PipelineSteps.vue:230` and `PipelineEditor.vue` step-card border-left widths differ** - - PipelineSteps row: `border-left: 3px solid var(--step-color)`. - - PipelineEditor preview step: `border-left: 3px solid var(--step-color)` ✓ matches. - - DerivationRuleCard `:330`: `border-left: 3px solid var(--wp-kind-derivation, #fbbf24)` — same width but value-not-token-driven. - - Fix: tokenize as `--wp-kind-stripe-w: 3px`. - - Effort: S - -- [ ] **`Wildcards.vue` syntax pills hardcode `font-size: 9.5px` for `.pi`** - - File: `views/Wildcards.vue:457`. Smallest icon size in entire app. Other inline icons inside list cells are 11–13px. Visually this dot will look noticeably smaller next to `wp-row-fav-btn` (13px). - - Fix: bump to 10.5px or 11px. - - Effort: S - -- [ ] **Filter chip `border-radius: 999px` but tag chip `border-radius: 999px`, while filter-toggle in ModuleListView uses 11px** - - `components/ModuleListView.vue:635` (`.wp-filter-toggle { border-radius: 11px }`), but tag chips inside same toolbar use 999px. Same UI role (selectable chip), different shape. - - Fix: unify. - - Effort: S - -- [ ] **`Wildcards.vue:298,304` inline `style="color: var(--wp-success...)"` instead of class** - - Two occurrences in template for valid/invalid state icons. Mixes inline-color with the cleaner pattern used elsewhere (`class="wp-token-good"` etc.). - - Fix: introduce `wp-icon--success`/`wp-icon--warn` utility classes. - - Effort: S - -- [ ] **`AppTopbar.vue:33-35` ternary returns `var(--wp-success, #10b981)` etc. as strings** - - Three lines using inline-style ternary with token-fallback. Working but better as data-attribute + CSS. - - Fix: `:data-status="apiStatus"` + scoped `.wp-topbar-status[data-status="online"] { ...dot color }`. - - Effort: S - ---- - -## P2 — Nice-to-have / cleanup - -- [ ] **Inconsistent font-size literals throughout** - - Common values seen: `10px, 10.5px, 11px, 11.5px, 12px, 12.5px, 13px, 13.5px, 14px`. The half-pixel variants (`10.5/11.5/12.5/13.5`) make 9 distinct sizes for "small text". A 5-step scale (10/11/12/13/14) tokenized as `--wp-text-xs/-sm/-md/-base/-lg` would simplify maintenance. - - File hot-spots: `views/Wildcards.vue` (uses 10.5,11,11.5,12.5), `components/PipelineSteps.vue` (uses 9,10,11,11.5,12,12.5,13). - - Effort: L - -- [ ] **`wp-output-var` and `wp-row-expand__name` — same role, different class** - - `views/Combines.vue:330`, `views/Wildcards.vue:482`, `views/FixedValues.vue:307`, `views/Constraints.vue:368`. All use `color: var(--wp-accent-text, #c4b5fd)`. Two different class names for identical visual. - - Fix: pick one (`wp-row-expand__name` is more descriptive). - - Effort: S - -- [ ] **`wp-mono` used 70+ times alongside `font-family: var(--wp-font-mono)` literal in many places** - - Files: too many to list. Half use the class, half use the literal CSS in scoped style. - - Fix: prefer `wp-mono` class (already in tokens.css) — search-replace. - - Effort: M - -- [ ] **`Categories.vue` `wp-cat-table` reimplements `wp-table` styling instead of extending** - - File: `views/Categories.vue:268-289`. Defines own `thead th` / `tbody td` rules duplicating what `tokens.css` already has on `.wp-table`. Causes subtle padding diffs (`padding: 9px 12px` here vs whatever `.wp-table` defines). - - Fix: use `.wp-table` directly with column class modifiers. - - Effort: M - -- [ ] **`CommunityDiscover.vue:209` uses `style="background: var(--wp-bg-2)"` for skeleton loader** - - Inline `style=` for a static color. One-off. - - Effort: S - -- [ ] **Loading state inconsistencies** - - `CommunityDiscover.vue:208-213` uses skeleton cards with `aria-busy="true"`. - - `views/Wildcards.vue:174` uses `:loading="store.loading"` passed to ModuleListView (no skeleton; only a no-op flag that seems unused). - - `TestRunner.vue:433` has its own loading button (`:loading="running"`). - - Fix: define one shared `.wp-skeleton` block in tokens.css (already exists per the prototype convention) and use across loading states. - - Effort: M - -- [ ] **`wp-comm-tabs` / `wp-comm-tab` only in Community area** - - `views/community/CommunityProfile.vue:92-102` (uses tabs). No tab pattern in main app even though `Wildcards.vue` extra-filter chips serve a similar role. Not consolidatable here, but worth a note. - - Effort: - - -- [ ] **`HistoryPanel` mounts a teleport with raw `
` with cols: Name, Color (width 360), Modules (width 110), Actions (width 110, right) | Cols: Name, Color (240px), **Sort (90px — extra)**, Modules (90px), Actions (80px right) | Vue port adds a **Sort** column for `sort_order`. Prototype has no sort column. Width differs (240 vs 360 for Color). | -| Color cell | View: `{c.color}` (just the hex). Edit mode: `` (swatch + hex input + 8-color palette) | Always shows `` swatch + hex right next to it (`wp-cat-color`). | Vue port shows the picker inline at all times (no view/edit mode). Works but is busier. Different: prototype uses inline-edit pattern (one row at a time), Vue lets you change colors directly without click-to-edit. | -| Inline name edit | View: `{c.name}`. Edit mode (after pencil): `` with autoFocus + Enter to save | View: `{name}` clickable to enter edit. Edit: `` with Enter/Esc/blur to save. | **OK** UX, but the rendered chip is different (see §6 — solid bg + white text + text-shadow vs prototype's tinted Chip). | -| Sort cell | n/a | `` blurs to commit | Extra in Vue port. | -| Modules count | `` raw mono number | `{moduleCount}` styled as a pill with bg-3, 999 radius, padding 0/8 | Vue port turns the number into a pill — visually busier. | -| Empty state | `
No categories yet.
` (no CTA) | Same icon + `wp-dim` text (no CTA) | OK | -| Edit button | Pencil button per row to enter edit mode (sets `editing = {id, name, color}`) | Click directly on the chip name to inline-edit; **no pencil button** | Vue port skips the explicit pencil. Works but discoverability suffers. | -| Save/cancel buttons in edit mode | Two icon buttons in Actions cell: `
{count}
…
-
- -``` - -**Structural mismatch**: Vue port adds a `Sort` column + inline color picker + module-count pill. Prototype keeps it minimal: name chip, hex string, count, action icons. Recommend **moving Sort to a drag-handle column instead of a numeric input** to match `Pipeline` step rows pattern (see §15). - ---- - -## 8. Card - -### Prototype spec (`ui.jsx:370–384` + `styles.css:316–327`) - -```css -.wp-card { - background: var(--wp-bg-1); - border: 1px solid var(--wp-border); - border-radius: var(--wp-radius-lg); /* 12px */ -} -.wp-card__header { - padding: 12px 14px; - border-bottom: 1px solid var(--wp-border); - display: flex; align-items: center; gap: 10px; -} -.wp-card__title { - font-size: 12px; - text-transform: uppercase; - letter-spacing: 0.06em; - color: var(--wp-text-muted); - margin: 0; - font-weight: 600; -} -.wp-card__body { padding: 14px; } -``` - -Component renders: - -```jsx -
- {title && ( -
-

{title}

-
- {action} -
- )} -
{children}
-
-``` - -So `padded={false}` removes the inner padding entirely (used by Dashboard's Recent Edits list). - -### Hover/click states - -- Prototype `.wp-card` has **no** `:hover` rule. Static surface. -- `.wp-stat` (Dashboard stat card) is also static at the rule level (`styles.css:826–854`); the prototype only adds `cursor: pointer` inline when the card is keyboard-navigable (Dashboard.jsx:37). -- Cards inside the Discover screen have hover-lift (`.wp-comm-card:hover`, `styles-community.css:83–87`): - ```css - transform: translateY(-1px); - border-color: var(--wp-border-strong); - box-shadow: 0 6px 18px rgba(0,0,0,0.28); - ``` -- Featured (`.wp-comm-feat:hover`, `styles-community.css:34`): `transform: translateY(-2px); box-shadow: 0 8px 24px rgba(0,0,0,0.35);`. - -### Current `Card.vue` deviation - -- Adds `subtitle` prop + `.wp-card__title-wrap` (`tokens.css:350`) + `.wp-card__subtitle` block. Prototype has no subtitle in Card — uses `.wp-page__subtitle` at the page level only. Cosmetic addition. -- `slots.actions` rendered after a ``. Matches prototype. -- `padding` prop default `true`; matches `padded` prop in prototype. -- Header padding: prototype `12px 14px`; tokens.css value: `12px 14px` — matches. -- Body padding: prototype `14px`; tokens.css `14px` — matches. -- No hover on `.wp-card`. Same as prototype. - -**OK as-is.** - ---- - -## 9. ModuleListView toolbar (Search / Filters / Sort) - -### Prototype CSS - -The prototype's toolbar is a single flex row: - -```css -.wp-toolbar { - display: flex; align-items: center; gap: 8px; - flex-wrap: wrap; -} -.wp-toolbar__search { flex: 1; min-width: 220px; max-width: 320px; } -.wp-toolbar__count { font-size: 11.5px; color: var(--wp-text-dim); margin-left: auto; } -``` - -Markup (`lists.jsx:111–135`): - -```jsx -
-
-
- - -
-
- -
- ` (no `size`, width `160`) | **34px** (`var(--wp-input-h)`) | 12.5px | 7px | 1px solid var(--wp-border) | - -All three at the **same** 34px height in the prototype. Active filter count rendered inline as a `N`. - -### Current Vue port heights - -From `ModuleListView.vue:325–350`: - -| Element | Vue height | Source | -|---|---|---| -| Search `wp-input-group` | **34px** | `tokens.css:478` `.wp-input-group { height: var(--wp-input-h); }` | -| Filters ` -
…
- {children} -
-
- {hasHistory && } -
- - -
-
-
-``` - -**Mechanics**: - -- Footer height = `var(--wp-btn-h)` (34px) + `12px × 2` padding = **58px** total. -- The `::before` pseudo-element provides a 20px **top fade** so content scrolling underneath is visually softened. -- `position: sticky; bottom: 0` requires the **scrolling parent** to be the `.wp-content` (`styles.css:264-270`), not the page itself. -- Prototype `.wp-page` has `min-height: 100%` and `display: flex; flex-direction: column`. The footer's `margin-top: auto` shoves it to the bottom of the page; while scrolling, sticky pins it at viewport bottom. -- **Body padding-bottom**: there is no explicit `padding-bottom` on `.wp-page` for editors — the prototype relies on `margin-top: auto` pushing the footer flush against the next-to-last child. Because the footer has `padding: 12px 0` and no `position: absolute`, content above is naturally pushed up. There is **no overlap in the prototype** because the footer's height takes its own row in normal flow before sticky activates. -- **However**, the `::before` fade is positioned `bottom: 100%; height: 20px` — it only paints **above** the footer, not into it. So the fade is visible at the seam where content meets footer. - -### Current Vue port deviation - -`EditorFrame.vue` body styles (lines 109–124): - -```css -.wp-editor { - display: flex; - flex-direction: column; - gap: 16px; - padding-bottom: 96px; /* present */ -} -``` - -So the port **does** add `padding-bottom: 96px`. Why does the user see overlap? - -Likely causes (worth verifying live): - -1. **Inner content scroll**: `.wp-page--fill > .wp-table-wrap--scroll { flex: 1 1 auto; min-height: 0; overflow: auto; }` (`tokens.css:310–314`). Combined with `wp-editor`'s `padding-bottom: 96px` on the *outer* page, **the inner scroll container does not get the 96px buffer**. If the editor uses `wp-page--fill` (it does — `EditorFrame.vue:58`) and an inner scrollable area exists (e.g. the `Card` containing the rule list expands beyond viewport), then the table-wrap-scroll's last row sits flush against viewport bottom and the sticky footer paints over it. -2. The `position: sticky` footer needs the scroll container's height to be reduced so its content can be scrolled into view above the sticky element. With `padding-bottom: 96px` on `.wp-editor` (the flex child) + `wp-page--fill` height: 100% with overflow: hidden on the parent, the padding affects the wrong axis. -3. `EditorFrame` lacks the `::before` fade gradient that styles.css defines globally — but the global rule should cover it. - -**Recommended fix**: move `padding-bottom: 96px` to **the closest scrolling container** (i.e., on `.wp-page--fill` itself or on the wrapping `.wp-content` that scrolls). For editors that use plain (non-fill) pages, keep on `.wp-editor`. Alternatively, switch the footer from `position: sticky` to `position: fixed; bottom: 0; left: var(--wp-sidebar-w); right: 0` to mirror native PrimeVue toolbar pattern — but that requires sidebar-aware layout. - -Footer height in port: same 58px (token unchanged). - ---- - -## 11. Derivation rule card - -### Prototype rule (editors.jsx:565–636 + styles.css:657–768) - -The prototype renders **always-expanded** cards with a flat IF/ELIF/ELSE branch list. There is **no collapsed state** in the prototype. - -```jsx -
-
- RULE - #{ri + 1} -
-
- -
- {rule.branches.map((b, bi) => ( -
-
- {bi === 0 ? "IF" : "ELIF"} -
- {bi > 0 &&
-
- When - -
-
- Then - -
-
- ))} - {rule.hasElse && ( -
…
- )} -
- -
- - {!rule.hasElse && } -
-
-``` - -CSS (`styles.css:657–768`): - -```css -.wp-rule { - background: var(--wp-bg-2); - border: 1px solid var(--wp-border); - border-radius: var(--wp-radius-md); /* not defined ! See §16 */ - padding: 10px 12px; - display: flex; flex-direction: column; gap: 8px; -} -.wp-rule__head { display: flex; align-items: center; gap: 8px; } -.wp-rule__num { - font-family: var(--wp-font-mono); - font-size: 10.5px; font-weight: 600; letter-spacing: 0.05em; - padding: 2px 8px; border-radius: 4px; - background: color-mix(in oklab, var(--wp-accent-500) 18%, transparent); - color: var(--wp-accent-text); -} -.wp-rule__index { font-size: 11px; color: var(--wp-text-dim); font-variant-numeric: tabular-nums; } -.wp-rule__row { - display: grid; - grid-template-columns: 64px 1fr; - gap: 10px; - align-items: center; -} -.wp-rule__label { - font-size: 11.5px; color: var(--wp-text-muted); - text-transform: uppercase; letter-spacing: 0.04em; font-weight: 500; -} -.wp-rule__cond { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; } -.wp-rule--group { - border: 1px solid var(--wp-border-strong); - background: var(--wp-bg-1); - padding: 12px; -} -.wp-rule--group .wp-rule__num { - background: color-mix(in oklab, var(--wp-accent-500) 24%, transparent); -} -.wp-rule__branches { - display: flex; flex-direction: column; - gap: 8px; - margin-top: 4px; - padding-left: 10px; - border-left: 2px solid color-mix(in oklab, var(--wp-accent-500) 30%, transparent); -} -.wp-branch { - background: var(--wp-bg-2); - border: 1px solid var(--wp-border); - border-radius: 8px; - padding: 10px 12px; - display: flex; flex-direction: column; gap: 8px; -} -.wp-branch__head { display: flex; align-items: center; gap: 8px; } -.wp-branch__tag { - font-family: var(--wp-font-mono); - font-size: 10px; font-weight: 700; letter-spacing: 0.06em; - padding: 2px 7px; border-radius: 4px; text-transform: uppercase; -} -.wp-branch__tag[data-kind="if"] { background: color-mix(in oklab, var(--wp-accent-500) 22%, transparent); color: var(--wp-accent-text); } -.wp-branch__tag[data-kind="elif"] { background: color-mix(in oklab, var(--wp-info,#60a5fa) 22%, transparent); color: var(--wp-info,#60a5fa); } -.wp-branch__tag[data-kind="else"] { background: color-mix(in oklab, var(--wp-warn) 22%, transparent); color: var(--wp-warn); } -.wp-branch--else { border-style: dashed; } -.wp-rule__addbar { display: flex; gap: 6px; margin-top: 8px; padding-left: 12px; } -``` - -**Branch row layout**: - -- Each branch is a 10×12 padded panel inside the rule's `2px` accent left-border container. -- `When` / `Then` labels in 64px cells. -- Condition row uses `
` with `@` addon + input for the variable name + `` for value. -- Action row uses `` for target, `` for value. - -**No collapse mechanism** — every rule expands every branch all the time. With many rules + many ELIFs the editor gets tall (each branch ≈ 100px; rule with `IF + 2 ELIF + ELSE` is ~440px tall; 6 such rules = 2640px page). The user's "too big / no collapsing" complaint is **valid** in absolute terms but is **faithful to prototype behavior**. - -Default = expanded. Height per single-branch rule with no condition value: `~145px`; with `IF + ELSE`: `~245px`; with `IF + ELIF + ELSE`: `~345px`. - -### Compare to current `DerivationRuleCard.vue` - -Differences (`DerivationRuleCard.vue` 153–326): - -- **Markup wrapped in ``**: prototype uses raw `.wp-rule.wp-rule--group`. Vue uses `Card` primitive with no header (passes children directly), losing the `.wp-rule--group` background+padding+border-strong rule. The scoped `derivation-rule-card { border-left: var(--wp-kind-stripe-w) solid var(--wp-kind-derivation, #fbbf24); background: var(--wp-bg, #1e1e22); }` reads instead — different visual. -- **Header pill uses `Rule {n}`** at line 156. Prototype uses `.wp-rule__num` (mono caps "RULE" badge in accent color) + `.wp-rule__index` "#1". Vue replaces both with one info-toned pill — different look. -- **Adds a "branchCount" meta line** (`{n} branches + ELSE`) — not in prototype. -- **Move-up/move-down buttons missing** from header. -- **Action row split into TWO rows** (target+mode on one row, then a separate `value` row with `