release log

Changelog

Versioned record of what's shipped — components added, doctrine changes, breaking adjustments. Follows Keep a Changelog conventions and Semantic Versioning. The canonical source is CHANGELOG.md in the repo; this page is just a friendlier read of it.

Changelog

All notable changes to tti-ux. Follows Keep a Changelog conventions and Semantic Versioning.

Unreleased

Changed — navigation restructure, 9 groups to 7 (2026-09-01)

Sprawl pass. Four surfaces were answering two questions, two groups had three entries between them, and kit/ — nine shipped framework targets — had no page at all.

  • Composition, Forms and Tooling are gone as groups. Their entries redistribute: composed pages to Kits, Forms to a nested branch under Components, Accessibility and Contrast audit to Foundations.
  • New Kits group — "where do I look for an assembly of things." Composed examples, Patterns, and the frozen static reference designs.
  • Design narrows 11 → 7 by re-homing, not hiding. components.md now leads the Components group, chart-foundations.md the Visualizations group, compositions.md the Kits group. Every doc keeps a sidebar home and its /design/<slug> URL; only ownership moved.
  • /preview relabelled Specimens in the sidebar. Route unchanged.

Added — Install pages for the kit/ tier (2026-09-01)

  • /install — one page for all nine framework targets, with design/kit-pipeline.md as its doctrine body. Deliberately a page, not a group: a directory listing wearing an intent label is the thing that was already rejected as "Kit targets" / "Platforms" / "Integrations".
  • /install/power-bi — platform-level Power BI setup, rendering kit/powerbi/README.md verbatim so the site and the npm package are single-source.

Changed — Power BI moves out of design/ (2026-09-01)

design/powerbi-kit.md is deleted; its content merged into kit/powerbi/README.md. design/ is doctrine about TUX itself — how to apply TUX to a specific host is a different kind of document. The doc was also the proof: registering a design doc takes three edits (design/index.vue, design/[doc].vue, and app.vue), and it shipped with only two, so it rendered but never appeared in the sidebar.

Added — Power BI tabs on component pages (2026-09-01)

The seam: how to style one chart lives on the chart; how to set up a report lives in the setup page. Implementations are an attribute of a component, not a section of the nav — which is why there is no Power BI group.

  • TuxExample gains a powerbi tab, pushed conditionally exactly as source already was. tableau slots in the same way.
  • Wired on chart-bar, chart-line, chart-area, chart-scatter (the chart-cartesian fragment) and data-table (table-chrome), imported from kit/powerbi/ rather than retyped, so a tab cannot drift from the emitter.
  • Coverage is partial and says so. The kit ships three fragment types, not one per chart, so a parity table on the Visualizations overview names what is covered and what is not — a missing tab reads as "no fragment yet", not "Power BI can't render it".

Added — Power BI kit design doc (2026-09-01)

  • design/powerbi-kit.md, surfaced at /design/powerbi-kit by the existing design/*.md glob. Covers why the kit is shaped the way it is: the no-native-dark-mode constraint that splits it into theme vs PBIR, the named-theme-colors trap, the version-pinning burden, Fluent 2, and the known gaps. The consumer procedure stays in kit/powerbi/README.md, which ships in the package.
  • Deliberately a design doc rather than a bespoke page. The whole framework-target tier is undocumented on the site — kit/csharp, kit/react, kit/wp, kit/css, kit/scss have no pages either — so this sets a pattern those can follow instead of being a one-off. Note /reports would be the wrong home despite the name: that section is explicitly for finished narratives the reader doesn't pivot or filter, and a Power BI report is the opposite.

Changed — chart foundations §2 settled (2026-09-01)

  • design/chart-foundations.md §2 is no longer a TODO. Building the Power BI visualStyles emitter forced every axis, gridline and legend choice to become a literal, and two systems now have to agree on them.
  • No --chart-* tokens were minted. The 2026-05 skeleton expected four new anchors; the surface and text ramps already carry those roles, and aliasing would create two names for one value — which audit:tokens flags. §2 now maps each role to the existing token and its Power BI counterpart.
  • Records one deliberate divergence — web gridlines use --surface-border at 50% opacity, the Power BI theme uses surface.border-subtle, because a theme's gridlineColor has no alpha channel — and one defect: TuxChartLine styles axis ticks --text-muted while TuxChartBar uses --text-secondary. Both clear AAA, so it is a consistency bug, not an accessibility one. --text-secondary is named canonical; moving TuxChartLine is left to its own change, since it is a visible change to a shipped component.

2.1.0 — 2026-09-01

Added — Power BI kit v2: PBIR components (2026-09-01)

The Power BI target grows from a colours-and-fonts theme file into a drop-in report kit. Ported from the Power BI & Fabric documentation tree in docs-tti-tamu-edu, re-tokenised to TTI, and re-pinned against the current Microsoft schemas (every version below verified live on 2026-08-31: each URL returns 200 and the next version up 404s).

  • scripts/pbir-schema-lock.mjs — single source for every pinned PBIR $schema URL and format-version literal, emitted to kit/powerbi/pbir/schema-lock.json. Current tips: report/3.3.0, visualContainer/2.9.0, page/2.1.0, pagesMetadata/1.1.0, platformProperties/2.1.0 (under gitIntegration/, notitem/report/), definitionProperties/2.0.0, versionMetadata/1.0.0. npm run verify:pbir re-checks all of them against the live endpoints.
  • kit/powerbi/tti-theme-hc.json — third emitted theme, from the existing tti-hc tokens. Has no upstream equivalent.
  • kit/powerbi/pbir/geometry.json — report shell layout: chrome bands, content area, nav-pill stride, and the z-order scheme (content reserved to 9000–15000). Two canvas variants, because Fluent 2 moved the default canvas to 1920×1080 while existing reports stay at 1280×920.
  • kit/powerbi/pbir/fragments/ — card chrome, tableEx chrome and cartesian-chart chrome, in four lanes: static tti / tti-dark / tti-hc, plus themed whose colours bind to DAX measures.
  • kit/powerbi/pbir/tmdl/ + pbir/dax/ — the in-report light/dark switch: a ThemeMode table and 30 TuxThemeColors measures across eight display folders, as TMDL for PBIP projects and as DAX for Desktop. Power BI has no native dark mode for report content in Desktop, the service, or mobile, so this is the mechanism, not a workaround. Shell* roles are theme-invariant (the masthead stays maroon); Tooltip* roles invert.
  • kit/powerbi/pbir/shell/{classic,fluent2}/ — 15 drop-in visuals per canvas: 10 page-chrome visuals plus 5 nav-pill templates, laid out one folder per visual to match PBIR on disk. All 30 validated against the live visualContainer/2.9.0 schema with its five remote $refs resolved. Includes the Dark/Light tile slicer (mode: 'Basic' andorientation: "1D" — both required) with cross-page syncGroup.
  • kit/powerbi/README.md — drop-in procedure, the theme-registration triple, the semantic-model contract, and the calibrated sizing rules.
  • npm run build:pbir / verify:pbir, with build:pbir wired into build:kit.

Changed — Power BI theme emitter (2026-09-01)

scripts/build-powerbi-theme.mjs now emits a complete report theme. Consumers re-vendoring at a pinned tag will see visible differences:

  • Removed the _generated key. The theme schema is additionalProperties: false over exactly 42 named properties, so every theme v1 emitted was technically invalid. Provenance moved to kit/powerbi/README.md.
  • background (dark) #15100F#221F1F. v1 mapped it to surface.page, but Power BI's background is the visual fill; it is now surface.raised, so dark cards separate from the canvas.
  • tableAccent (dark) #5C0025#6BB4C0. v1 pinned light-theme maroon regardless of theme, which reads poorly on a dark card; it now follows the theme's own brand anchor.
  • Added $schema, a baseTheme pin (Fluent2-CY26SU08CY24SU06 is two generations stale), the structural set (firstLevelElementsfourthLevelElements, secondaryBackground, shapeStroke, disabledText, hyperlink, visitedHyperlink), a brand-anchored divergent ramp, and visualStyles for 26 visual types.
  • Exports generate(), so tests/tux-kit-targets.test.ts can byte-lock the output. build-powerbi-theme.mjs was the only Tier-1 emitter with no lock test — it could drift from tokens silently, which is precisely the failure class its own header comment claims to end.

Added — Atlas portal tile (2026-08-31)

  • atlasAtlas, https://atlas.tti.tamu.edu, audience:"entitled", placed beside landscape (the two gated portals sit together). The site returns 403 to non-members, so like Landscape it enforces its own access and the tile is hidden rather than shown-and-refused.
  • _meta.version 2 → 3. Anonymous now sees 4 of 6.
  • Entitlement is resolved per-portal server-side; TTI Code gates this tile on the Entra group that governs it today. Nothing about the mapping is in this file — see design/unification-plan.md (Registry doctrine).

Changed — TTI Portals roster (2026-08-30)

  • design/apps.json _meta.version 1 → 2. New roster order: tti-wwwtti-mytuxlandscapetti-code.
  • ai-studio removed from the roster (owner call). The /launch/ai-studio interstitial, the forge's repo-header "Open in AI Studio" button, and the ratified desktop-link law all survive — the tile is gone, the integration is not. With no kind:"desktop" entry left, that law is dormant; tests/useTuxApps.test.ts now asserts the zero-count so the dormancy is explicit rather than a vacuously green test.
  • tti-my addedmy.TTI, the internal site at https://my.tti.tamu.edu. Shipped audience:"public": the site enforces its own SSO, and authenticated is enforced upstream but is a no-op in the forge deployment (its waffle renders outside the signed-in guard), so shipping it would create a field that means one thing here and nothing downstream.
  • tti-www repointed to the apex https://tti.tamu.edu (verified HTTP 200 with zero redirects; www. 301s to it) and renamed TTI.
  • audience:"entitled" remains ADVISORY upstreamapp/app.vue renders <TuxUtilityCluster current="tux" /> with no entitled prop, so ux.tti.tamu.edu shows public tiles only. Enforcement is a per-portal obligation, and TTI Code ships the first resolver: Landscape's tile is never baked and never published, and is added per-caller from Entra security-group membership. See design/unification-plan.md (Registry doctrine, amended 2026-08-30).

Added — framework targets, tier 1 (2026-08-19)

  • kit/csharp/TuxTokens.cs — resolved token literals for .NET consumers: one static class per theme (Tti / TtiDark / TtiHc) with PascalCased consts + an All dictionary for dynamic lookup.
  • kit/react/tux-tokens.ts — per-theme resolved token maps for React/TS apps outside the Nuxt layer, plus tuxVar() for the CSS-custom-property spelling when the CSS kit is loaded.
  • kit/wp/theme.json — WordPress block-theme settings (v3): 29-entry TUX palette, the four families + mono as fontFamilies, radius/wash/rhythm under settings.custom.tux.
  • All three come from scripts/build-framework-targets.mjs, which shares build-tokens.mjs's selector builder and resolves var() chains, aliases, and wash color-mix to literals per theme (dark washes correctly compute over the teal anchor). Wired into npm run build:kit; tests/tux-kit-targets.test.ts locks the committed outputs to the generator and pins resolution semantics.
  • Multi-language monorepo + @tti/tti-ux-react (owner-ratified: ports live in this repo). npm workspaces; packages/react ships @tti/tti-ux-react (version-locked, same tag train, published by the extended publish workflow). First port as proof: TuxBigStat — identical props/BEM/tokens, byte-equivalent CSS, 4 port-fidelity tests, ledger entry pinned to the source hash. Repo .npmrc maps the @tti scope to the Forgejo registry (the same line consumers add).
  • Port-writer bot specced (kit-pipeline.md): tux-port-bot service account leveraging the superPOD models via the BFF like the existing bots (dedicated PAT + actor attribution), writing drafts into the drift-bot's PR shape; degrades to detection-only until the owner mints the account. Platform direction recorded for TTI Code: per-repo toggleable bots, security review non-optional.
  • Port-drift bot (tier 2, detection stage)ports-sync.yml runs on every merge to main touching Tux*.vue: updates the port ledger (kit/ports/manifest.json, 153 components tracked by content hash) + kit/ports/QUEUE.md, pushes a bot branch, and opens a PR that the existing gates — including the ai-review superPOD panel — judge like any human PR. Built entirely on existing access (job token + the ai-review bot PAT); no new BFF endpoints or tokens (owner decision). The ledger's --check mode runs in the test suite, so a component can't land without the ledger following it.
  • design/kit-pipeline.md — the two-tier doctrine: tier 1 (deterministic emitters, fully automatic, zero AI, publish on tag) vs tier 2 (component ports via the TTI AI BFF superPOD panel: automatic generation + mechanical verification + PR-gated delivery; proposed architecture documented, not yet built).

2.0.0 — 2026-08-19

The language major. Everything a consumer touches — prop words, emit names, token set, package identity — is now the ratified vocabulary, released as one coordinated break before the multi-target token expansion (C# / React / WordPress kits) multiplies the consumer count.

Fixed

  • Wash tokens resolved to nothing between the language-closeout merge and this release: the mechanical wash sweep had also rewritten the generated tokens.css, turning the --wash-brand-* definitions into self-references (--wash-brand-4: var(--wash-brand-4)), which are invalid at computed-value time. Regenerated from source; audit:tokens now hard-fails on any self-referential token definition so this bug class can't ship again.

Breaking — package identity & distribution

  • The package is now @tti/tti-ux, published to the Forgejo npm registry (code.tti.tamu.edu/api/packages/tti/npm/) on every version tag — Forgejo is the canonical install source. Migration: add the @tti scope to .npmrc, depend on @tti/tti-ux, and use extends: ["@tti/tti-ux"] in nuxt.config.ts. The old github:ttitamu/tti-ux#vX.Y.Z pin keeps working for existing consumers but is deprecated — the GitHub repo is a mirror whose job is the GitHub Pages deploy of ux.tti.tamu.edu, nothing more. See README § Consuming from another app.

Breaking — v2.0.0 vocabulary batch (2026-08-19)

The canonical prop/emit vocabulary (components.md § "Prop & emit vocabulary") is now enforced by the APIs themselves. Migration is mechanical:

ComponentBeforeAfter
TuxFactoiddensity: 3|4|5columns: 3|4|5
TuxBetaRibbontone: preview|beta|devkind (same values)
TuxTabsintent: default|boldvariant (same values)
TuxChartGaugeband intent: ok|warn|alertband tone: success|warning|error (band/fill CSS hooks renamed to match)
TuxCodeMaroonseverity: alert|warning|infotone: error|warning|info (--alert classes now --error)
TuxTestimonialcolortone (same values)
TuxConfirmDialogvariant: dangerremoved — use destructive
TuxConversationList / TuxSuggestionChipsemit pickemit select
TuxCitations / TuxInlineCitationemit openemit select
TuxStatComparison / TuxMapLegendlayout: stacklayout: stacked
TuxFigureCaption / TuxTableCaptionkind (free-form label)label
TuxRichTextEditorduplicate bare update emitremoved — use update:modelValue

Added (non-breaking, same batch): TuxTreemap now emits the family hover payload ({ path, size, depth, childCount } \| null).

Changed — language closeout (2026-08-19, second pass)

  • The wash ladder (visual-language-evolution.md § Batch L): all transparent-tint washes collapse from 22 eyeballed color-mix percentages onto the ratified ladder {4 · 6 · 8 · 12 · 18 · 22 · 35 · 50}; maroon washes use new named tokens --wash-brand-* (theme-aware via the brand anchor). ≥60% scrims exempt.
  • Chart tooltip chrome consolidated into tux-chart-chrome.css (.tux-chart-tooltip shell + --flip), removing nine near-identical scoped blocks; treemap keeps only its wider max-width and now shares the family border (was a one-off maroon border).
  • On-brand ink correctness in dark mode: white-on---brand-primary controls (rich-data-grid selection/bulk bar, pagination active, alpha-nav active) now use --text-inverse — dark ink on the dark theme's light teal anchor instead of broken white-on-teal. TuxSignupFeature's maroon panel moves to --brand-fill + --text-on-brand (stays maroon in every theme, per the brand-fill doctrine); its whites/alphas tokenized.
  • Guards: tests/tux-color.test.ts enforces the wash ladder and freezes raw color literals per component at an exact-budget ratchet (sanctioned chrome documented inline); scripts/audit-tokens.mjs no longer blanket-exempts Tailwind prefixes the app also owns (the hole that let --radius-full ship undefined).

Changed — visual-language unification pass (2026-08-19, first pass)

One deep pass to make the whole catalog speak the same language, driven by three full-repo sweeps (typography, color/tokens, API vocabulary) plus a light/dark browser walk. Fix highlights:

  • Typography: the four-family rule is now real. All 78 references to the deprecated --font-sans (Public Sans) are gone from app code — chart roots and body text moved to --font-body; TabBar / MenuBar / FAB label controls moved to --font-bold; the token itself now aliases the body stack for legacy consumers. Dead var(--font-x, var(--font-y)) fallback chains stripped. Stat values that wore Oswald outside its H1–H3 lane (TuxResearcher / TuxLab / TuxProgram metrics, TuxStatComparison, donut + gauge center stats) now use the TuxBigStat numeral face (--font-body 700 + tabular figures). Numeric readouts (chart tooltip values, legend stats, R², code-block line numbers) join the --font-mono + tabular-nums pattern; counting affordances (composer counter, facet counts, sparkline delta, file sizes) gain tabular figures. Mermaid diagrams read the live --font-body token instead of a pasted stack. tests/tux-typography.test.ts now guards all of it in CI.
  • Elevation system actually adopted. All hand-rolled box-shadows and legacy --shadow-sm/md/lg component references migrated to the four-tier --elevation-* tokens (identical values in light/dark — but the high-contrast theme suppresses blur shadows through the elevation tokens only, so every migrated site now renders correctly in HC). TuxRichDataGrid's hand-rolled focus ring now uses the universal --shadow-focus two-ring.
  • --radius-full finally exists. Six components referenced it before it was ever defined — an invalid var() that rendered suggestion chips, removable chips, info labels, and context meters square instead of pill. Token added (9999px), literal 9999px sites converted, wrong-value radius fallbacks (var(--radius-sm, 4px)) cleaned.
  • Gold-surface ink promoted to tokens. The 12 pasted #2A0E15 ("ink on gold") and 6 pasted #A87B1F (gold gradient deep stop) literals became --brand-accent-ink / --brand-accent-shade.
  • TuxChartHeatmap dark-mode fix. The dark theme reverses ramp luminance, so the stop-indexed white/dark label split inverted there; in-cell values now use --text-primary with a --surface-page paint-order halo — ≥18:1 against the halo in every theme, on every ramp stop.
  • TuxActivityTimeline marker glyphs use --text-inverse (theme- aware) instead of #fff; the Landscape-dashboard example's hand- rolled activity rail now dogfoods TuxActivityTimeline dense.
  • Vocabulary doctrine. components.md § Conventions gains "Prop & emit vocabulary" — canonical meanings for tone/kind/variant/intent/size/density/layout and the hover/select/update:* emits, with every known deviation listed and queued for v2.0.0 (renames are breaking; they land together, not piecemeal).
  • Docs truth: tux.md's dark-theme row now matches the ratified teal anchor (#6BB4C0) with the tokens.json rationale; showcase page titles normalized to the 2-part TuxX · TUX form.

Added

  • TuxChartHeatmap — native SVG matrix heatmap, the top-ranked viz gap from the 2026-08 audit (time-of-day patterns: crashes by day × hour, corridor demand by month). Cells quantize onto the same 5-stop sequential ramps the choropleth family uses (--map-seq-maroon-* default, slate variant), equal-interval bins with the ranges printed in the legend, honest hatched null cells ("No data", never zero), auto-thinned dense column axes, and the family tooltip/keyboard contract with a two-dimensional roving cursor (Left/Right walk columns, Up/Down walk rows — documented in components.md § Chart tooltips). Demo at /visualizations/chart-heatmap.
  • TuxChartHistogram — native SVG distribution chart over raw samples, built for the travel-time-reliability shape. Bin edges snap to 1/2/5 × 10^k (tuxBinEdges) so ranges read "10–15 min"; :percentiles="[50, 95]" drops dashed gold rules at interpolated quantiles (the planning-time-index pair); normalize switches the y axis to share-of-samples for side-by-side comparisons; family tooltip/keyboard contract (band-mode roving cursor via useTuxChartHover). Demo at /visualizations/chart-histogram.
  • TuxActivityTimeline — editorial vertical event timeline (the audit's top component gap): maroon spine, mono tabular timestamps, gold-ringed current node, eyebrow-weight heading group rows, semantic tones shared with the badge/alert families, dense rail posture, #item / #trailing scoped slots. Stateless — the host owns the feed. Demo at /components/activity-timeline.
  • app/utils/tuxChartBins.ts — shared binning + quantile math for the distribution charts (tuxBinEdges, tuxBinCounts, tuxQuantile/tuxQuantileSorted, tuxQuantizeBin), sibling to tuxChartScale, behavior-locked by tests/tux-chart-bins.test.ts.
  • Chart motion vocabulary extended in tux-chart-motion.css: heatmap cells fade in column-staggered; histogram bars grow from the baseline like the bar family. Both collapse to instant under prefers-reduced-motion: reduce.

1.9.0 — 2026-08-12

The chart-engineering minor. One audit-driven day: the geographic family stops shipping 348KB of Texas to every consumer (per-kind async split), the four cartesian charts trade four drifted copies of their scale/hover math for shared modules under behavior-lock tests, the palette collapses ~135 pasted declarations into one file, and the six interaction-less charts join the tooltip/keyboard contract — including the treemap keyboard-drill fix and a scatter roving cursor replacing 500 tab stops. Underneath: the repo's first mounted component tests (vitest + @nuxt/test-utils), VueUse/TanStack dead weight dropped in favor of tux-owned composables, TuxRichDataGrid gains an opt-in virtualized big-list mode, focus rings unify onto the universal two-ring system, and a fix wave closes the tux-audit dependency gate, Math.random() ids, the radial charts' SSR float mismatches, and the TuxExample formatter crash that had been corrupting hydration on heavy pages.

Added

  • TuxRichDataGrid virtualized big-list mode (owner-requested) — pass virtualized (+ optional virtualRowHeight, default 44) and the body windows through @tanstack/vue-virtual: only the visible slice of rows renders, bracketed by table-compatible spacer rows that preserve the scroll extent, so sticky header, sorting, and key-based selection keep working (measured live: 17–25 rendered <tr>s while scrolling 5,000 rows). Spacers are mounted-gated so SSR and the hydrating client agree. Row expansion is disabled while virtualized (variable-height windows need per-row measurement — build when a consumer needs it). Demo on /components/rich-data-grid; mounted tests included.
  • Mounted component testing foundationvitest.config.ts (the file never existed; npm test ran bare) with a two-tier layout: pure tests stay in the node environment, mounted tests opt into the nuxt environment per file (@nuxt/test-utils + @vue/test-utils, jsdom DOM), so Nuxt auto-imports resolve exactly as in the app. First five mounted suites (18 tests) lock the hand-rolled behaviors nothing exercised before: the donut/scatter/treemap interaction contracts (arrow cycling, Escape, drill-in/up, roving cursor, tooltip cards + emits), TuxFocusView's Escape handling and aria-labelledby wiring, and TuxAnnouncementBanner's localStorage-backed dismissal memory. Contributor guidance in components.md now lists npm test among the pre-push guards.
  • Chart tooltip contract extended to the interaction-less charts (per the components.md "Chart tooltips" doctrine, which previously only Line/Bar/Area/Scatter honored):
    • TuxTreemap — cells were focusable but bound no key handlers (a focus ring with no readout and no way to drill). Cells are now real role="button"s with accessible names; focus shows the tooltip anchored to the cell, arrows walk the leaf cells, Enter/Space drills in, Backspace drills up, Escape dismisses.
    • TuxChartDonut — per-slice hover + branded tooltip card (label, value, % of total), sibling slices dim while one is active, native <title> fallbacks, hover emit, tooltip prop, and single-tab-stop keyboard access (arrows cycle slices).
    • TuxChartSunburst — same treatment; arrows walk segments in hierarchical order (group, then its children) and the tooltip names the parent group.

Changed

  • Focus rings: one universal system, ten divergent recipes removed. The layer has always shipped a themed two-ring focus affordance (*:focus-visible--shadow-focus, black-on-white in high-contrast), but ten components had grown their own competing rings — four recipes including a non-existent --focus-ring token (masked by fallbacks) and a brand-accent ring on TuxTreemap — that either double-drew on the universal shadow or suppressed it with outline: none. All removed; components now inherit the universal ring. Two sanctioned exceptions are documented in components.md §"Focus rings": SVG child elements (box-shadow can't render there — TuxTreemap cells now use the themed --focus-ring-outer instead of brand-accent) and forced-colors blocks (system colors by design). Form-control :focus border tints are kept — they're input affordances, not rings. Scope note: the reduced-motion blocks flagged in the same audit stay colocated per component by design (portability beats DRY for 3-line media queries), and small-type utilities are deferred until the token question is settled.
  • **Dependency hygiene (owner-decided): @vueuse/core + @vueuse/nuxt
    • @tanstack/vue-table removed.** All three were declared (VueUse even registered as a module) with zero call sites — dead weight in every consumer's dependency graph. The duplicated hand-rolled patterns they would have covered are now tux-owned composables: useTuxPersistedRef (SSR-safe storage-backed ref with a serializer option so migrated components keep their exact wire format) and useTuxClipboard (copy + "Copied" flash, keyed variant, clears its reset timer on unmount — the hand-rolled copies leaked theirs). Migrated: TuxAnnouncementBanner (dismissal memory), TuxCodeBlock / TuxExample / TuxCitationExport (copy affordances). @tanstack/vue-virtual stays — kept deliberately to back TuxRichDataGrid's virtualized big-list mode (owner-requested, landing separately).
  • TuxChartScatter keyboard model: roving cursor instead of per-point tab stops. Every dot was previously tabindex="0" — a 500-point scatter injected 500 tab stops with no Escape. The svg is now the single focusable surface; arrow keys walk points in x order with the active dot ringed, Escape clears, and per-dot pointer hover still works. The components.md contract text was updated to match.
  • Chart family: shared scale math + hover model (app/utils/tuxChartScale.ts + useTuxChartHover(), locked by tests/tux-chart-scale.test.ts). TuxChartLine / Bar / Area / Scatter (plus Sparkline / Treemap / CorridorStrip spot fixes) now share one implementation of nice-ticks, extents, domain padding, series tones, margins, and the roving-cursor hover/keyboard contract. Deliberate unifications: ticks — Bar/Area/Scatter adopt Line's algorithm (1/2/5-step, in-domain), so the same domain renders the same axis on every chart and Scatter's drifted ceil no longer drops the first tick; keyboard — arrow navigation seeds at the midpoint everywhere (Bar seeded at 0) and Up/Down alias Left/Right everywhere; tones — series 9+ clamps to --chart-8 everywhere (Line previously wrapped back to hue 1); margins — left/bottom are family-standard (48/36) so stacked exhibits baseline-align; hover — pointer over plot padding clamps to the edge index instead of Bar's old clear-on-padding; robustness — extents are loop-based (Math.min(...arr) overflowed the stack past ~100k points). Palette: series colors now flow through one --tux-chart-tone custom property set by shared tux-chart-tone--c1..8 classes (app/assets/css/tux-chart-palette.css) — ~135 duplicated var(--chart-N, #hex) declarations across six components collapse to one file; rendered colors verified computed-identical to the chart tokens on every showcase surface. Components keep their …__series--cN classes as consumer styling hooks.
  • TuxChartGeographic per-kind code split — the five kinds now render through async child components (TuxChartGeoCounty / Districts / UsContext / DotDensity / Flow, internal, no catalog entries) that each statically import only their own geo data module. app/assets/geo/texas.ts split into texas-outline / texas-outline-polygon / txdot-districts / tx-metros (data byte-identical; texas.ts remains as a compat re-export barrel) and scripts/build-geo.mjs now emits that layout. Payload per kind drops from ~348KB eager to ~12KB (flow) / ~21KB (dot-density) / ~63KB (county) / ~119KB (districts) / ~163KB (us-context); the public API, rendered SVG, and SSR/prerender output are unchanged.

Fixed

  • TuxExample HTML-tab formatter crash + the hydration errors it caused. The inline pretty-printer treated Vue's hydration comment markers (<!--[--> / <!--]-->) as open tags — every marker leaked one indent level forever (a rich-data-grid preview leaked +190, inflating 26KB of DOM to 165KB of output), and <col>-class void elements leaked too. On large, churning previews the quadratic runaway exceeded V8's maximum string length ("RangeError: Invalid string length"); because that threw during the hydration mount tick, Vue's reconciliation corrupted and logged four "Hydration completed but contains mismatches" errors on /components/rich-data-grid. The formatter now lives in app/utils/tuxFormatHtml.ts (unit tested): comments tokenize as single units and are dropped as framework plumbing, the void-element list is complete, and input is hard-capped with a visible truncation notice so no preview subtree can ever OOM the tab. Verified: the page loads with zero console errors and the HTML tab renders comment-free at true DOM depth.
  • Radial charts: SSR hydration mismatch from raw trig floats.TuxChartSunburst / TuxChartDonut / TuxChartGauge serialized unrounded Math.sin/Math.cos results into arc d and position attributes; the server's and browser's V8 can differ in the last ulp on transcendentals, so hydration flagged mismatched attributes ("Hydration completed but contains mismatches", data-dependent). Coordinates now round to 2dp at the geometry boundary — the same discipline the cartesian charts already used via toFixed(2) — verified byte-identical server-vs-client on every radial showcase instance. Also fixed in passing: TuxChartScatter's tooltip swatch lost its color in the palette consolidation (its hoverToneClass helper predated the shared tone classes).
  • tux-audit a11y gated on the wrong dependency — the dispatcher required puppeteer, but audit-a11y.mjs runs on jsdom + axe-core (its header documents why). Consumers with jsdom + axe-core were wrongly blocked; consumers with puppeteer but no jsdom were admitted and crashed on import. The gate now checks jsdom; help text and README corrected to state the honest per-command dependency split.
  • Math.random() element ids replaced with useId()TuxFocusView's aria-labelledby title id was generated by a Math.random() computed (an SSR hydration mismatch when rendered open, and a re-roll risk); now a stable useId(). TuxRuleBuilder's internal group child now derives rule/group ids from a per-instance useId() seed + counter instead of Math.random().

1.8.0 — 2026-07-30

The unification minor — semantic tokens for the estate, the toast that was always promised, Nuxt UI theme hardening (hybrid form doctrine), consumer-debt fixes, and the kit as a real distribution surface (brand.env, Power BI themes, tux-audit). Estate-visible change: body copy moves Public Sans → Open Sans per the four-family rule.

Added — distribution artifacts for non-Vue consumers (2026-07-30)

One source (design/tokens.json), five generated artifacts — this is how the Forgejo overlay, compose stacks, and Power BI reporting stop hand-vendoring hex:

  • kit/ + scripts/ now ship in the package files — git-dep consumers previously couldn't even see the kit the README sells.
  • kit/env/brand.env (npm run build:brand-env) — 71 flat resolved TUX_<THEME>_<GROUP>_<TOKEN>=#hex keys for shell/Go/Python consumers, written with collab-hub's temp-file + atomic-rename fail-safety so an interrupted run can never half-theme downstream apps.
  • kit/powerbi/tti-theme.json + tti-theme-dark.json (npm run build:powerbi) — Power BI report themes: 10-series dataColors from the chart ramp (chart-9/10 added so 10-series validators are token-sourced, not hand-mixed), tux fonts, and a theme-invariant brand block per the on-brand rule. The reporting repo re-vendors at pin-bump instead of maintaining its v1.1.0-era hand snapshot.
  • npm run build:kit chains tokens → brand.env → powerbi.
  • kit/README gains the "which artifact for which consumer" matrix.

Added — tux-audit bin: guardrails as a consumable (2026-07-30)

  • npx tux-audit tokens [dirs…] — the zero-dependency undefined-token audit, now runnable from any consumer (one CI line, documented in the README). audit-tokens.mjs generalized: target dirs via args/TUX_AUDIT_DIRS, token definitions harvested from the layer AND the consumer, extra namespaces via TUX_AUDIT_EXTERNAL_PREFIXES. Validated against the estate: the Landscape frontend passes clean; the AI Studio tree fails with the migration table printed — exactly the 17-undefined-tokens bug class this exists to catch.
  • tux-audit contrast / tux-audit a11y delegate when the consumer has puppeteer/axe-core and explain honestly when it doesn't.

Added — TuxStatusToast + useTuxToast (2026-07-30)

  • The catalog's biggest hole, and a phantom the docs promised since the motion table and tauri-bindings.md first cited it: TuxStatusToast (the once-per-shell host) + useTuxToast() (the bus). Polite role="status" live region; error tones render role="alert" and stick until dismissed; slide-from-edge + fade at --motion-fast/--ease-standard, collapsing to opacity-only under reduced motion; optional action button; edge="top" mount for workbench shells. In Tauri shells an unfocused window ALSO fires an OS notification (guarded dynamic import of @tauri-apps/ plugin-notification, capability-optional, silent no-op on web) — the tauri-bindings.md contract implemented. The docs shell dogfoods the host; /components/status-toast is the live demo. Owns its own item state rather than piggybacking Nuxt UI's toaster so the semantics and motion are tux-controlled; migration from useToast() is a rename.

Added — TuxAvatar + TuxPageContainer (2026-07-30)

  • TuxAvatar — photo/initials/status-dot identity primitive, extracted from TuxUserMenu (which now consumes it; no API change). Broken photo URLs fall back to initials; decorative by default.
  • TuxPageContainer — content-width primitive over the new --layout-content-max (80rem) / --layout-content-wide (96rem) / --layout-prose-max (72ch) tokens. Landscape alone carried three divergent max-widths before this existed.

Added — Nuxt UI theme hardening, Batch K (2026-07-30, owner-ratified)

The hybrid form-control doctrine lands: U* primitives are the blessed app controls and the layer brands them everywhere —

  • Controls (button/input/select/textarea/[role="button"]) ride --font-bold (Work Sans) per the four-family rule.
  • Nuxt UI's --ui-radius binds to --radius-md, aligning U* corner geometry with Tux chrome.
  • The four family fonts are now Tailwind utilities (font-body/display/bold/elegant).
  • TuxButton re-scoped in doctrine as the editorial button; components.md's "Form input" row rewritten for the hybrid rule.

Added — TuxBadge tone="custom" (2026-07-30)

  • The open-palette escape hatch docs-tti's StatusPill needed: tone="custom" + --tux-badge-bg/fg/border hooks (which must point at tokens — consumer audit:tokens runs keep that honest).

Fixed — consumer-debt fixes (2026-07-30)

  • TuxMegaMenu caps its panel at the viewport below the nav and scrolls internally — Landscape deletes its app.vue override.
  • TuxSiteNav + TuxAppFrame publish --tux-nav-height on <html> (ResizeObserver-measured) — consumers replace hardcoded 4rem/5rem sticky offsets in both shell shapes.
  • Layer route stripping: consumers no longer inherit the style guide's 164 showcase routes via extends (opt back in with TTI_UX_DEMOS=1) — docs-tti deletes its pages:extend workaround.
  • Dark-bridge tolerance: the Nuxt UI .dark bridge now matches any *-dark color-mode value (not just tti-dark) and honors a tux: { darkBridge: false } app.config kill-switch — docs-tti stops path-stripping the plugin.
  • README documents the reverse-proxy icon: { mode: "svg" } guidance (the Landscape/Caddy ghost-icon failure).

Added — semantic tokens for the estate (2026-07-30)

  • --text-on-brand — theme-invariant near-white for content on --brand-fill surfaces, in all three themes. The collab-stack's --tux-on-brand insight made canonical: fixed-maroon surfaces must never use --text-inverse (it flips dark in dark mode). Adopted by TuxCTA, TuxFooter, TuxPageHeader, TuxAnnouncementBanner (replacing 10 hardcoded #fff declarations) and registered as the measured pair on /contrast-audit, so AAA CI now gates the real token. Tailwind alias --color-text-on-brand added.
  • --surface-border-subtle — the hairline-divider step below --surface-border consumers kept inventing as --border-subtle (light: neutral-150; dark: #2E2B2A; hc: deliberately === border).
  • Spacing scale--space-2/8/12/16/24/32/40/48/64 join the existing 0/4/10, closing the "spacing is un-tokenized" gap against tux.md principle 4.
  • Migration table (kit/README.md + audit:tokens failure message): every consumer-invented token name observed in the estate mapped to its canonical equivalent, plus the documented on-brand recipe and the "rename, don't alias" doctrine — no compat aliases ship.

Changed — warning is TTI gold (2026-07-30, owner-ratified)

  • Nuxt UI's warning alias now maps to the TTI gold ramp (--color-gold-*, #DDAC37 anchor) instead of the amber stand-in the app.config self-documented as a follow-up. Landscape's local warning: "gold" override becomes redundant at its next pin-bump.

Changed — typography now matches the four-family doctrine (2026-07-30)

Estate-visible at pin-bump. tux.md's four-family rule was doctrine the CSS didn't follow; now it does:

  • Body copy (html, body) and .eyebrow/.subhead use --font-body (Open Sans) — previously --font-sans (Public Sans, the "legacy" face per tux.md).
  • .heading--display uses --font-display (Oswald in the default style, re-binding to Work Sans / Georgia under .style--bold / .style--elegant) — previously Public Sans 800 italic. Italic dropped (Oswald ships no italic axis); weight normalized to 700.
  • --font-sans remains defined (self-hosted) but is deprecated — marked in tokens.json, tux.md, and globals.css.
  • Dead @nuxt/fonts dependency removed (module was already out of nuxt.config; fonts are self-hosted via fonts.css + sync-fonts.mjs); stale comments claiming otherwise fixed in globals.css and tux.md.

Fixed — layer no longer bleeds docs-site build config (2026-07-30)

  • The $production.nitro.prerender block (crawlLinks + failOnError: false) is now fenced behind an is-root-project check, so extends consumers stop inheriting the style guide's prerender behavior. Landscape can delete its nitro:config reset hook at next pin-bump.

1.7.1 — 2026-07-30

Doctrine + registry-truth release — docs and CI only, no runtime changes. Safe for all consumers to pick up immediately.

Added — unification doctrine committed (2026-07-30)

  • design/unification-plan.md — the portal-unification plan's permanent home. The v4 plan document (authored + seven-lens-reviewed in-session on 2026-07-28) previously lived only in session state; it is now reconstructed as public-safe doctrine: the two-shape identity rule, one-chrome-many-voices, registry law, distribution artifacts, guardrails, execution log, and the open-decision ledger. Registered in the Design sidebar, the design index, and the [doc].vue title map (which also gains the missing roadmap and visual-language-evolution titles).
  • compositions.md §Suite chrome — the section TuxUserMenu/TuxUtilityCluster docblocks have cited since v1.7.0 but which was never written: canonical header composition per shell shape, the cluster anatomy law, the two-shape identity rule as composition law, and the don't list.
  • Owner decisions ratified 2026-07-30: form controls go hybrid (harden the layer's Nuxt UI theme; no wrapper set; TuxButton re-scoped editorial — lands v1.8.0), warning → TTI gold (v1.8.0), collab-stack hc-as-light confirmed accidental, docs-platform work spun into its own workstream.

Added — catalog single source + registry test (2026-07-30)

  • app/utils/tuxCatalog.ts — one 139-entry catalog (name, route, icon, family, kind, wraps, blurb) now drives the sidebar navTree's Components / Reports / Visualizations groups, the /components index card grid, and the homepage count. The four hand-maintained lists it replaces had drifted: ~30 components missing from the index grid (the entire research-publishing, TTI-identity, geospatial, and forms families), 9 from the sidebar (including v1.7.0's own TuxUserMenu + TuxUtilityCluster), 3 from components.md (backfilled: TuxRailNav, TuxUserMenu, TuxUtilityCluster).
  • tests/tux-catalog.test.ts — catalog ↔ filesystem ↔ routes ↔ components.md invariants in the useTuxApps.test.ts mold. The icon check immediately caught 10 deprecated Lucide alias names (form-input, alert-circle, check-square, …) that the nav had carried silently — all renamed to canonical.
  • npm test now gates CI (quality job) — vitest existed since v1.7.0 but ran in no workflow.

Fixed — doc truth pass (2026-07-30)

  • README: "Current release: v1.4.2" (three releases stale) replaced with a CHANGELOG pointer; pin examples bumped #v1.4.2#v1.7.0; component census corrected (~130+5 → 140+6, now test-enforced); the "Landscape — formerly Landscape" rename artifact fixed; kit/ and the apps registry documented; ADR count corrected (ten → twelve); useTuxApps added to the composables list.
  • Homepage: hardcoded "70+" count now computed from the catalog; recent-updates feed gains the missing v1.7.0 suite-chrome entry.
  • getting-started.vue counts (~100/~95) corrected; components.md "~70 components" corrected and phantom references annotated (TuxPhotoCard → roadmap; TuxStatusToast → ships v1.8.0); roadmap.md ship-checklist now points at the catalog + test instead of four files.

1.7.0 — 2026-07-28

Suite-chrome release — the portal unification plan's v1.7.0 slice.

Added

  • TTI Portals registry: design/apps.json (public tile metadata only) + useTuxApps() with audience filtering (public / authenticated / entitled, Tier-0 fail-open) and filtered-count footer text. App lists are never hand-declared in consumers (doctrine).
  • TuxUtilityCluster — the trailing app-control cluster: search · notifications · theme · waffle · identity in law-fixed DOM order; registry-fed waffle; theme toggle announces via role="status".
  • TuxUserMenu — the suite's one identity affordance: cluster + rail-footer mounts, state-complete (loading / signed-out / signed-in / local-only / error), law-fixed menu order.
  • vitest + registry-invariant unit tests (tile-position constancy, audience filtering, no client-side entitlement mappings).

Changed

  • TuxAppSwitcher A2b pre-flight: registry order everywhere (current-sorts-last removed), focusable current tile with clean aria-current, list semantics, same-tab default, "Desktop app" affix, reserved presentation prop (popover|sheet), motion tokens, forced-colors fallbacks, heading "TTI Portals".
  • Docs site dogfoods the suite chrome: header uses TuxUtilityCluster (waffle + theme; identity seat deliberately absent).
  • Live PECAN copy retired (READMEs, examples, demo poster, PECAN_TOKENLANDSCAPE_TOKEN). Historical records keep the old name.

Fixed

  • TuxSiteNav focus restoration (A2c): both outline: none suppressions removed — keyboard focus is distinguishable from hover again (WCAG 2.4.7) and survives forced-colors.
  • Five undeclared @tiptap dependencies declared (v1.6.1); postinstall guarded for git-dep consumers; brace-expansion pinned 5.0.8 (CVE-2026-14257).

1.6.0 — 2026-06-05

Additive release (no breaking changes) — two new component capabilities requested by the docs-tti consumer. Cut from the v1.5.0 tag (does NOT include the in-flight Bootstrap-theme / token-pipeline work on main).

Added — TuxBadge tone prop

  • tone (info | success | warning | error | neutral) — a generic semantic-color badge, the open-ended sibling of tier / status. No lifecycle affordance (no status dot, no spinner). For inline doc labels like "Work in Progress", "Available", "Evolving Standard" where the classification-tier / lifecycle-status semantics don't fit.

Added — TuxCard linked prop + attribute forwarding

  • linked — renders the linked-card chrome (hover-lift + corner arrow) on a plain <div> instead of an anchor. For cards whose real link lives on an inner element (a title link with a stretched ::after), is EXTERNAL, or that contain several interactive children an anchor wrapper would swallow. to still wins when both are set.
  • Attribute forwardingTuxCard is a multi-root component (its three branches), so Vue couldn't auto-inherit fallthrough attrs. Added inheritAttrs: false + explicit v-bind="$attrs" on each root so consumers can reliably pass class / id / etc.

1.5.0 — 2026-06-02

Added — Tightened the authoring loop (2026-06-02)

Two new guards + a toolchain fix, after a session-state review found that defects were consistently caught in batch audits after shipping rather than during authoring.

vue-tsc resolution fixed. nuxt typecheck was resolving vue-tsc from the global npx cache while vue-router's volar plugin (vue-router/volar/sfc-route-blocks) loaded from local node_modules and then failed to require('@vue/language-core') (split dependency trees). The typecheck still exited 0, but the route-block plugin was silently not running — so typed <route>-block checks were skipped. Added vue-tsc + @vue/language-core to devDependencies so the whole toolchain resolves from one tree; the plugin now loads.

npm run audit:tokens (new — scripts/audit-tokens.mjs). Static pass asserting every var(--token) reference in app/**/*.{vue,css} resolves to a token defined in the CSS, a var(--x, fallback), or a known external namespace (Tailwind / Nuxt UI). Catches the --surface-base class of bug (undefined token → transparent render) that's invisible to typecheck (CSS isn't typed) and the contrast audit (no color to measure). Now blocking in CI's quality job.

npm run audit:a11y (new — scripts/audit-a11y.mjs). Promotes the ad-hoc "tested via jsdom + axe-core" pass into a committed, repeatable gate: runs axe-core over every prerendered page (color-contrast disabled — that's the contrast audit's job). The first full run found 135 structural violations across 42 pages that the per-batch ad-hoc checks had never covered site-wide; all are now fixed (see below). Now blocking in CI's audit job. Added axe-core to devDependencies.

Fixed — Site-wide a11y remediation (2026-06-02)

Drove audit:a11y to zero across all 164 pages. Highlights:

  • Charts (TuxChartArea / Bar / Line / Scatter, TuxDiagram) — aria-label on bare SVG shapes (<rect> / <circle>) is prohibited without a name-supporting role; added role="img" to labeled data / interaction shapes. TuxDiagram mermaid output is legalized via a post-processor; its loading skeleton got role="status".
  • TuxFileDropzone + /forms/file-upload — converted the role="button" drop zone wrapping a focusable <input type="file"> (critical label + nested-interactive) to a native <label> wrapper: one focusable, named control, no nesting.
  • TuxSuggestionChips — moved role="listitem" off the <button> onto a wrapping element (aria-allowed-role).
  • TuxCodeMaroon <aside role="alert"><div role="alert">; TuxSplitPane detail <main><section role="region"> (no duplicate / nested main); TuxRichDataGrid empty <th> → visually hidden text; TuxAbstract / TuxAcknowledgments hardcoded <h4> → configurable level prop (heading-order).
  • TuxCardCarousel — dropped the redundant outer region landmark (Nuxt UI's UCarousel already is one) and named that region instead.
  • TuxToc showcase wrapper <aside>/over-eager <nav> → plain <div>; tree / sunburst callout <aside><div> (landmark-complementary-is-top-level).
  • Landmark components (alpha-nav, branch-nav, breadcrumbs, link-slab, pagination, site-nav, stepper, tab-bar, treemap) — added optional ariaLabel props and gave each repeated showcase instance a unique accessible name (landmark-unique, which fired because galleries render a landmark component 5–7×).
  • Doc pages — index-grid card headings <h3><h2> (no level skip under the page <h1>); /kits/slides + /kits/aggieux content wrapped in a <main> landmark (region); icon-only buttons on app-frame / swipe / text-field given aria-label (critical button-name).

Added — TuxRailNav (2026-06-02)

Collapsible sidebar/rail navigation built on native <details>/<summary> (the TuxFilterPanel "zero-JS, perfect-a11y" disclosure pattern). Grouped entries with one nesting level, an icon-only collapsed mode, and a <nav> landmark named via an overridable ariaLabel prop. Showcase at /components/rail-nav.

This is the tux-owned replacement for Nuxt UI's UNavigationMenu in vertical mode, which renders a collapsible group's chevron as a roleless <span aria-expanded> (axe aria-allowed-attr — internal to Nuxt UI, present in 4.7.0 and still in 4.8.1, not fixable from the consumer side). Swapped into both example shells (/examples/sidebar-shell, /examples/landscape-dashboard), which closes the last a11y violation at the sourceaudit:a11y is now a true zero with no suppressions. (A KNOWN_UPSTREAM allowlist remains in scripts/audit-a11y.mjs, intentionally empty, as the documented home for any future genuine third-party artifact.)

Fixed — Polish pass (2026-05-26)

After the seven-slice add pass, an audit pass caught a token bug and three a11y violations.

--surface-base was undefined — every component shipped in this batch (TuxCommentThread, TuxPopover, TuxMcpEmbed, TuxRuleBuilderGroup, TuxRichTextEditor, TuxMobileFrame, plus two showcase pages) referenced var(--surface-base) as a screen / content background. That token doesn't exist in tokens.css — only --surface-page, --surface-raised, and --surface-sunken do. The undefined token fell back to no value, so backgrounds were transparent and whatever showed through looked wrong in both light and dark mode. Swapped to var(--surface-raised) across all 7 files — that's the equivalent TUX token for component-surface backgrounds (cards, modals, popovers, artifact bodies). Reported by the user: "light mode has dark mode colors."

nested-interactive on TuxMobileFrame — the frame's <figure> carried role="img", but the slot can hold focusable children (e.g. the demo CTA button). axe's nested-interactive flagged it; dropped the role and let <figure> carry its native semantic role instead.

landmark-unique on TuxRuleBuilder — the top-level wrapper was <section aria-label="Rule builder">, which gives it a landmark role; two builders on one page (flagship + empty demo) collided. Switched to a plain <div> since the surrounding heading already names the surface.

Pre-existing TUX-wide fixes — caught while we were here:

  • <html> element was missing lang — added htmlAttrs: { lang: "en" } to nuxt.config.ts app.head. axe's html-has-lang rule (serious impact) had been failing on every page.
  • TuxDocsSidebar was rendering <aside role="navigation">, which axe's aria-allowed-role flags — aside's implicit complementary role can't be overridden to navigation. Switched the outer element to native <nav> (carries the navigation landmark natively) and collapsed the now-redundant inner <nav> to a <div> to avoid duplicate landmarks.

After fixes: zero axe violations on all 6 showcase pages from this batch (mobile-frame, comment-thread, popover, mcp-embed, rule-builder, rich-text-editor). Tested via jsdom + axe-core (added jsdom to devDeps).

Added — TuxMobileFrame (2026-05-26)

Closes the deferred Priority E "Mobile frames" entry. Stylized device chrome for marketing screenshots and stakeholder reviews — one component, two platforms via a platform="ios" | "android" prop. (Initially shipped as two separate TuxIosFrame + TuxAndroidFrame components pointing at one combined showcase page; consolidated to a single component when the dual nav entries made the same showcase highlight twice in the sidebar.)

Explicitly not for runtime use. When TTI apps actually need mobile chrome, reach for TuxAppFrame + TuxTabBar + useTuxPlatform() per design/platform-awareness.md. The new frame only ships the static device chrome around a slot; the screen doesn't react to safe-area insets, platform APIs, or orientation. Doctrine is documented in the showcase's "Frame vs runtime chrome" decision-tree footer.

  • TuxMobileFrameplatform="ios" renders realistic iPhone 16 Pro proportions (19.5:9 screen, ~46px corner radius scaled, Dynamic Island, home indicator, four finishes: natural / black / white / maroon). platform="android" renders Pixel 9 proportions (20:9 screen, ~36px corner radius, center punch-hole camera, three nav-style options: gesture / three-button / none, four finishes: obsidian / porcelain / hazel / maroon). Single :width prop drives proportional scaling of all chrome — pass 280 (default), 160 for thumbnail rows, 360 for hero exhibits. Both platforms share the same width pivot so they sit cleanly side-by-side in cross-platform mockups.

Showcase at /components/mobile-frame — flagship demo per platform with a realistic Landscape corridor- summary slot; color-variant rows; Android nav-style picker; the canonical side-by-side cross-platform layout; and the frame-vs-runtime decision tree.

Implementation is pure CSS — no SVG, no images, no canvas. The device body uses a single <figure> with absolutely-positioned side-button pills and an inner <div class="…__screen"> holding the slot. Everything scales from one numeric :width prop.

Added — TuxRichTextEditor (2026-05-26)

Closes the deferred-with-criterion Priority D carry-forward — Tiptap- based WYSIWYG sister to TuxMarkdownEditor. Built to be the canonical TUX rich-text surface — feature set mirrors the TipTapEditor in docs-tti-tamu-edu/nuxt-site/app/components/ so the same editor can land in Landscape draft surfaces, tti-ai-studio chat composition, and the docs.it.tamu.edu admin center without each consumer maintaining a separate Tiptap wrapper.

  • TuxRichTextEditor — Tiptap-based editor with seven toolbar groups (configurable via :toolbarformat · headings · lists · block · media · table · mode). v-modeled as HTML.
    Inline marks: bold · italic · underline · strikethrough · inline code.
    Headings: H1 · H2 · H3 · H4 (per-consumer subset via :heading-levels).
    Lists: bullet · numbered · task (checkable, nestable).
    Blocks: blockquote · code block with syntax highlighting via lowlight (covers ~30 popular languages via common) · horizontal rule.
    Media: link (⌘K prompt) · image (URL + alt-text prompt, base64 allowed).
    Tables: insert 3×3 with header row · add / remove columns and rows · resizable column widths · selection highlight · delete table. Table-specific buttons only appear when cursor is inside a table.
    Mode toggle: WYSIWYG ↔ raw HTML source textarea. Round-trips cleanly in both directions; toolbar buttons disable in source mode; a "source" pill appears in the footer to make the mode visible. (Markdown ↔ HTML round-trip via turndown is deferred to a follow-on — the :sourceFormat prop is the planned extension point for when a consumer surface stores markdown.)
    Full-screen: overlay mode that pins the editor to the viewport via position: fixed; inset: 0; z-index: 60; Esc exits. Emits @fullscreen-change.
    Word / character count: live footer; pure DOM-text count, ignores HTML tags. Hide via :show-count="false".
    Save event: ⌘S / Ctrl+S intercepted via Tiptap's handleKeyDown and emitted as save. Host wires to persist.
    Typography substitutions: smart quotes, em dashes, ellipses (Tiptap Typography extension).
    Undo / redo: disabled-state derived from editor.can().

Bundle. Tiptap core / vue-3 / starter-kit / link / image / placeholder / underline arrive transitively via @nuxt/ui 4.7 (no package.json add). The richer extensions — tables (4 packages), task lists (2 packages), typography, code-block-lowlight, and lowlight itself — are direct dependencies pinned to NuxtUI's exact Tiptap version (3.22.4) to avoid peer-dep churn. Total package.json additions: 9 packages, all on the established Tiptap line NuxtUI already pulls.

Showcase at /components/rich-text-editor with three demos (full feature set with pre-seeded corridor field notes including a table + task list + Python code block · trimmed inline-comment field · minimal source-mode-only chrome), the feature inventory, and the "why this is the canonical surface" reference back to the docs-tti-tamu-edu editor.

Added — TuxRuleBuilder (2026-05-26)

Closes the deferred-with-criterion Priority B candidate. Relational query UI — field + operator + value rows, AND/OR groupers, nestable groups. Sister to the existing faceted TuxFilterPanel; they compose well on the same surface.

  • TuxRuleBuilder — top-level wrapper that owns the field catalog (passed via :fields) and footer actions (Apply / Clear). v-modeled as a single root Group tree. Provides the field catalog + max-depth to recursive children via inject.
  • TuxRuleBuilderGroup — recursive internal child (same pattern as TuxTreeNode inside TuxTree). Renders one group: a combinator toggle (AND/OR), an ordered list of mixed rules and nested groups, "+ Add rule" / "+ Add group" affordances. Recurses on itself for nested groups, bounded by the parent's maxDepth prop (default 3 — deeper trees almost always want to be split into saved views).

Five operator families ship with defaults: string (contains, equals, starts with, not contains), number (=, ≠, <, ≤, >, ≥, between), date (on, before, after, between), select (is, is not), boolean (is true, is false). The value editor switches based on field type + operator: text input · numeric input · date picker · select · "between" pair · no-input for boolean. Per-field operator overrides via FieldDef.operators.

Showcase at /components/rule-builder with a corridor-research demo exercising number between, select, nested OR group, and a live JSON pretty-print of the applied tree so hosts can see exactly what they need to evaluate against.

Conventions doc updated — new design/components.md § "Faceted vs relational filtering — when to use which" decision tree

  • anti-patterns table. Lives next to "Empty states" in the Conventions section.

Added — TuxMcpEmbed (2026-05-26)

Sister to TuxArtifact for the interactive MCP shape — when an assistant turn hands off to a third-party app (Linear, Notion, Figma, …) and renders the app's UI inline. The split:

  • TuxArtifact — static AI-generated output (code file, doc, image). File-chrome action set (copy / download / regenerate / share).
  • TuxMcpEmbed — interactive third-party app surface. Window-chrome action set (collapse / expand / exit). Built-in skeleton.
  • TuxMcpEmbed — app identity bar (icon + name + optional source line for version/scope) + window controls + skeleton + container slot. Three controls, individually toggleable (:collapsible, :expandable, :closable); each emits a semantic event (collapse, expand, exit) the host wires up. Internal collapsed state syncs from a prop so the host can persist + restore. Loading skeleton is three shimmer bars + a content block; honors prefers-reduced-motion with a static fill. Visual chrome matches TuxArtifact (same border / radius / head rhythm) so the two read as a family — the 3px accent strip on the left edge is the only signal that this is third-party, not first-party output. Showcase at /components/mcp-embed with three demos (Linear default · Notion loading · Figma fixed-chrome)
    • the MCP three-tier display taxonomy reference.

Conventions doc updateddesign/components.md § "MCP tool output — inline card / inline carousel / full screen" was promoted from single-component (TuxArtifact everywhere) to the static-vs- interactive split. The inline-card tier now branches on whether the MCP result is read-only (TuxArtifact) or app-like (TuxMcpEmbed); the full-screen tier documents the @expand-event → focus-view pattern.

Added — TuxPopover (2026-05-26)

Closes the deferred-with-criterion Priority D entry — the richer-than-tooltip floating panel that consumers were composing on top of UPopover directly (~7 surfaces in the catalog by the time this shipped: TuxFootnote, TuxInlineCitation, TuxContextMeter, TuxInfoLabel, TuxFormField, TuxAppSwitcher, plus TuxFilterPanel chips).

  • TuxPopover — title + body + optional actions panel. Default mode="click" because hover-with-actions is bad UX (panel dismisses the moment the user reaches for a button); mode="hover" available for read-only inspect cases. Editorial maroon hairline rule under the title (same anchor as TuxTooltip's title presentation — that's the visual cue that distinguishes a TUX floating panel from a bare UPopover drop). Three width tiers: sm (16rem), md (22rem, default), lg (28rem), plus auto. body prop covers prose; #body slot opens the surface to richer markup (lists, data blocks, form fields). #actions slot renders into a footer with a top border; bare <button> elements get compact chrome via :deep(button); add the tux-popover__action--primary class for a maroon primary action. Catalog convention added: the "which floating-panel component?" decision tree (tooltip / teaching popover / popover / bare UPopover) lives in the showcase page's footer for now; promote to design/components.md Conventions if a third consumer surface asks. Showcase at /components/popover.

Added — TuxCommentThread (2026-05-26)

Closes the only fully-unblocked Priority D entry in design/roadmap.md — peer-review / editorial-comment threads. Sister to TuxReactionBar: that's light-touch acknowledgement, this is the heavyweight thread surface for actual dialog.

  • TuxCommentThread — stateless renderer over a v-modeled CommentThread[]. Each thread groups a root comment + replies, has an open / resolved status, and supports @mention tokens (auto-detected on render, brand- accent color). Authors are passed in as a Record<string, CommentAuthor> directory (name + affiliation + optional avatar; initials fallback when avatar is absent). The host owns persistence — mutations emit semantic events (comment:add, comment:edit, comment:delete, thread:resolve, thread:reopen). Container-query density fallback below ~22rem for narrow sidebar slots; explicit size="sm" for the same effect via prop. Resolved threads collapse behind a "Show resolved (N)" toggle by default. Edit / delete affordances only render on comments the current viewer authored. ⌘↵ / Ctrl↵ in the reply textarea submits. Showcase at /components/comment-thread.

Roadmap entry struck through; component table updated; nav slot added between TuxCommandPalette and TuxConfirmDialog.

Dogfood/examples/paper-page now embeds an "Open peer-review" section between Discussion §4 and Acknowledgments. Three threads (two open + one resolved) reflect a realistic TRR open-review pass: reviewer raises a methods concern, author responds; reviewer suggests adding citations; reviewer flags a reading-grade issue on the plain-language summary, co-author addresses it. Typed fixtures live in paper-page.demo-data.ts per ADR-0011 since vue-tsc can't narrow string-literal union types declared inline in page <script setup> blocks.

Added — polish & hygiene sprint (2026-05-23)

Four-item polish pass closing the catalog + CI + theme gaps that accumulated through the four-family expansion. No new components; all behavior-preserving except where the typecheck cleanup fixed real bugs.

Catalog hygiene — three orphaned components surfaced:

  • TuxShortcutsHelp shipped into the catalog: row added to design/components.md, entry added to the /components card grid and app.vue sidebar nav, new showcase page at /components/shortcuts-help built on the TuxCommandPalette showcase template (combos vs sequences, native <dialog> setup, defineShortcuts wiring).
  • TuxTreeNode and TuxDocsSidebarNode annotated as internal children of TuxTree and TuxDocsSidebar in design/components.md — they're recursive rendering primitives, not standalone consumables.

Dark theme tokens aligned — the tti-dark runtime palette (150-line [data-theme="tti-dark"] block in app/assets/css/tokens.css) is now mirrored in design/tokens.json under themes.tti-dark (brand, surface, text, semantic, focus, chart, map sections) so the documented single source of truth matches what actually renders. New "Dark theme" section in design/palette.md explains the maroon→lifted-teal shift on small chrome elements, the three channels that preserve maroon brand presence (brand.fill for marketing panels, chart.1 pinned to wine-rose for the primary chart series, Nuxt UI's --color-maroon-400 override for solid buttons), and the asymmetry where tti-dark defines semantic/chart/map overrides that tti and tti-hc inherit from base.

CI gatesnpm run lint script added; new quality job in .github/workflows/audit-contrast.yml runs lint + typecheck in parallel with the contrast audit. Both gates are blocking after the typecheck cleanup below. Fixed 3 pre-existing lint errors in scripts/audit-contrast.mjs (duplicate node:fs import) and scripts/build-geo.mjs (dead roundCoords function). Added reference/** to eslint.config.mjs ignores since that directory is sync'd from upstream and not ours to lint.

Pre-commit hook — husky + lint-staged installed. .husky/pre-commit runs npx lint-staged; package.json lint-staged config runs eslint --fix on staged .vue / .ts / .js / .mjs files. Husky's prepare script auto-installs the hook on npm install (documented in README.md Run-it section).

Typecheck cleanup (21 errors → 0) — typecheck CI gate flipped from non-blocking to blocking. Eleven files touched, mix of real bug fixes and pre-existing pattern issues:

  • TuxMapEmbed.vueiframeEl.value?.complete is an HTMLImageElement property, not an iframe one. Replaced with contentDocument?.readyState === "complete" (still null for cross-origin maps, which fall through to the load event).
  • TuxReactionBar.vue — replaced counts[r.key] !== undefined && counts[r.key] > 0 with (counts[r.key] ?? 0) > 0 so TS can narrow through the indexed access.
  • TuxChartLine.vue — added as [number, number] cast to the yDomain [0, 1] fallback so the destructured lo/hi aren't number | undefined downstream.
  • TuxChartScatter.vue — narrowed type predicate from (x): x is string to (x): x is "positive" | "negative" | "flat" to satisfy "predicate's type must be assignable to parameter's type".
  • TuxTable.vue — relaxed readStatus signature from { original?: Record<string, unknown> } & Record<string, unknown> to { original?: unknown } so it accepts Row<unknown> from UTable's slot.
  • TuxAppFrame.vue — replaced ternary emit(action === … ? … : …) with explicit if/else branches per emit name so TS can match the discrete tuple overloads.
  • TuxCardCarousel.vue — added props capture + carouselItems computed that casts through unknown to satisfy UCarousel's strict CarouselItem[] prop without losing the generic T for the #item slot.
  • TuxConfirmDialog.vue — narrowed confirmIntent return type to "primary" | "destructive"; mapped the "warning" dialog variant to the primary button intent since TuxButton's Intent doesn't include "warning" and the warning context lives in the dialog chrome, not the button.
  • TuxFormField.vue — kept kebab-case slot bindings (eslint vue/attribute-hyphenation requires it) + added a comment pointing consumers at the string-key destructure pattern ({ 'aria-describedby': x }).
  • app/pages/components/forms-wrapper.vue — switched the TuxFormField slot destructures to string-key syntax to match Vue's literal type extraction for aria-* props.
  • app/pages/components/rich-data-grid.vuerows.slice(0, 5)compactRows (undefined identifier reference).
  • New app/pages/visualizations/chart-gauge.demo-data.ts + app/pages/visualizations/chart-line.demo-data.ts — showcase data extracted from the .vue pages so we can use real TS annotations to narrow Band.intent and tuple-typed Series.band / Ref<[number, number]> without violating ADR-0010's macro-extractor constraint. See ADR-0011 for the prescribed pattern.

Architecture decision:

Added — four-family expansion (2026-05-22)

22 new components across four families, shipped in 4 focused commits with shared showcase routes:

Research-publishing cluster (8 components, /components/research-publishing): TuxAbstract, TuxAuthorByline, TuxPaperMeta, TuxFigureCaption, TuxTableCaption, TuxFootnote, TuxCitationExport (6 formats), TuxAcknowledgments. Makes "TUX is for research publishing" tangible at the page level.

TTI identity / brand cluster (5 components, /components/tti-identity): TuxResearcher (3 layouts), TuxLab, TuxProgram (3 layouts), TuxFundingSource (3 sizes), TuxCenterBadge (6 canonical TTI centers). Profile-shaped components for surfacing TTI people, labs, programs, funders.

Geospatial / map cluster (4 components, /components/geospatial): TuxMapEmbed (library-agnostic iframe + slot wrapper), TuxCorridorStrip (native SVG 1-D corridor viz with segments + events + value spark), TuxMapLegend (stack/inline/gradient layouts), TuxMapMarker (5 research-typed SVG markers). Extends TuxChartGeographic toward general geospatial.

Forms wrapper cluster (5 components, /components/forms-wrapper): TuxFormField (label + help + input + error stack with auto- generated ids + aria plumbing), TuxMarkdownEditor (no-deps markdown authoring with toolbar + shortcuts + preview), TuxFileDropzone (drag-and-drop uploads with validation), TuxValidationSummary (top-of-form error list with jump-to- field links), TuxConfirmDialog (TuxModal preset, 4 variants).

Showcase convention shift: families share a single combined showcase route rather than 22 individual /components/<kebab> pages. Reads better as a cluster + reduces nav bloat.

~132 Tux* components + 3 composables total (was ~110).

Added — composability doctrine + touchups (2026-05-22)

New doctrine doc:

  • design/compositions.md — "Compositions worth knowing" doctrine. Captures the "X + Y composes more value than they do alone" patterns that emerged across the chart-family, platform-aware, and AI-studio sprints. Seven sections (layout shells, headlines + summaries, chart surfaces, browse + detail, chat, cross-app nav, editorial) with code snippets + in-repo consumer citations + non-obvious gotchas. Three tests for what earns a slot. Composability is now a first-class doctrine, not scattered hints in component JSDocs.

Navigation:

  • Design-docs nav now lists Compositions, Chart foundations, and Platform awareness alongside Doctrine / Components / Palette / Roadmap. Previously these were only reachable via direct URL.
  • app/pages/design/[doc].vue title map extended for all current design docs so the browser tab + breadcrumb read right.

Touchups — motion vocabulary aligned in example surfaces:

  • app/pages/examples/research-landing.vue — TuxStatComparison delta row gains tux-mount-in stagger so the year-over-year numbers tick in subtly.
  • app/pages/examples/sidebar-shell.vue — Recent-activity rows fade up with stagger as the page mounts.

Added — second polish queue (2026-05-22)

Four-phase polish following the chart-tooltip + entrance-animation sprint. Spans dashboard hover hygiene, opt-in component motion, ai-studio example refresh, and Conventions documentation.

Dashboard hover state

  • Chart tooltips on TuxChartLine, TuxChartArea, TuxChartBarauto-flip to the left of the cursor when the active index sits past 60% of the chart width. Avoids right-edge overflow in tight card containers (the throughput / tier / health row in landscape-dashboard, for instance).

Component motion — new app/assets/css/tux-component-motion.css (auto-imported via globals.css):

  • .tux-mount-in opt-in class — immediate children fade + 4px slide up on mount (220ms). Stagger via --tux-mount-stagger-index per child.
  • .tux-mount-in--section larger variant for section-level entrances (320ms).
  • .tux-mount-fade + .tux-mount-scale for chrome elements that shouldn't slide.
  • All three respect prefers-reduced-motion: reduce (collapse to instant).
  • Applied opt-in to landscape-dashboard.vue (KPI strip + 3-card health row) and tti-ai-studio-session.vue (right rail).

tti-ai-studio-session.vue refresh:

  • TuxAppSwitcher added to the page-header actions (AI Studio ↔ Landscape ↔ TUX docs).
  • TuxFocusView wraps the compare.py artifact for an "open in focus mode" affordance (the natural Artifact + FocusView pairing).
  • TuxReactionBar replaces the inline thumb buttons in the assistant turn's footer (less friction than a feedback form).
  • tux-mount-in entrance on the right rail.

Conventions docs (in design/components.md):

  • New "Motion primitives — six canonical transitions" subsection documenting the six existing patterns (Tooltip / Popover, Dropdown menu, Modal / Dialog, Slideover / Drawer, Toast, Accordion) plus the three component-level + six chart-family primitives. All collapse to instant on prefers-reduced-motion: reduce — non-negotiable.
  • New "Chart tooltips — consistent pattern across the family" subsection capturing the 7-point contract every interactive chart now follows (focus, highlight, branded card, auto-flip, hover emit, tooltip prop, SVG <title> fallback).

Added — chart polish queue (2026-05-22)

Polishes the native chart family for consistency and motion.

Tooltip parity across the chart family:

  • TuxChartArea — vertical guide + per-series focus dots + tooltip card (Line-style pattern, including stacked-total readout).
  • TuxChartBar — column-wash highlight + tooltip card with per- series values + projection-vs-actual comparison row + total.
  • TuxChartScatter — per-dot pointer enter / focus reveals a tooltip anchored to the active dot. Dot grows +2px on active. Native SVG <title> still present as a fallback for AT.
  • All three accept :tooltip="false" to disable, and emit a hover event with the active payload (consumer can sync hover state across panels).
  • Keyboard-accessible: tab into the plot, arrow keys cycle indices on Line/Area/Bar; tab into a dot on Scatter.

Chart entrance animations (new app/assets/css/tux-chart-motion.css, auto-imported via globals.css):

  • Lines draw on via animated stroke-dashoffset (450ms).
  • Bars scale-Y from baseline (vertical) / scale-X from edge (horizontal), staggered ~12ms per category.
  • Areas + top-lines rise from a 0.94 vertical scale (360ms).
  • Donut slices spin in (~360ms, staggered 28ms per slice).
  • Scatter dots pop from scale(0), staggered 8ms.
  • Gauge fill sweeps via stroke-dashoffset; needle swings from -135° to its angle (480ms).
  • All keyed off prefers-reduced-motionreduce collapses every animation to instant.

TuxFocusView + chart composition:

  • Chart-line showcase gains an "Open in focus mode" demo that pins the chart full-viewport with brush + tooltip still functional inside the overlay.

Added — compositional sprint: chart interactions + dashboard refresh + absorption closure (2026-05-22)

Layered onto the chart-family completion: real interactions on the most-used chart, a full Landscape-dashboard dogfood, and the absorption pipeline closed.

Chart interactions on TuxChartLine:

  • Hover tooltip — on by default. Mouse + keyboard (tab into plot area, arrow keys to cycle data points). Tooltip card positioned with the active vertical guide line + focus dots on each series. Shows current + previous-period readouts together. Native SVG <title> fallback still present for plain screen- reader users.
  • Brush selector — pass brush + v-model:range to render a compact preview strip beneath the main chart with two draggable handles. Drag handles to resize the visible window; drag the window itself to pan. Charts UI Kit absorption carry-forward finally lands. Two new emits (update:range, hover).

Showcase refresh:

  • /visualizations/chart-line gains two new sections (tooltip
    • brush) with live demos.

app/pages/examples/landscape-dashboard.vue:

  • Three new chart sections dogfood the full Priority B chart family on real-shape Landscape data:
    • 12-month ingest trend with brush selector + previous-period overlay (TuxChartLine).
    • KPI strip (TuxBigStat × 4) above stacked area chart by file type — the canonical "summary + trend" composition from the Charts UI Kit absorption.
    • Throughput-by-weekday bar + access-tier donut + API-uptime + agent-pool gauges in a three-card grid.

Absorption pipeline closed:

  • Three sleeper audits — Accessible Design Toolkit (a11y rubric parity confirmed), Omnichart UX Flowchart (TuxDiagram covers it), 59 Charts UI Responsive (native SVG architecture reaffirmed). All "no new components" with documented rationale.
  • New reference/figma-cache/SKIP-RATIONALE.md consolidates the explicit-skip rationale for the remaining 22 files (other framework systems, mockup tools, single-pattern kits). Future contributors don't need to re-litigate.

Totals after this sprint: 46 / 70 absorbed; 22 explicitly skipped + 2 known duplicates = full corpus triaged.

Added — native chart family completion (2026-05-22)

Closes the Priority B chart roadmap. Four new native SVG charts land in one sprint, all following design/chart-foundations.md doctrine: maroon-led --chart-1..8 palette, end-of-element value labels colored to series, auto-derived screen-reader summary, editorial chrome via optional TuxChartFrame wrap.

  • TuxChartDonut — share-of-total with center stat slot. Slices below minSlice% auto-fold into "Other" (keeps it legible past 6 categories). Slice labels colored to the wedge they point at — color-blind users get identity from text adjacency.
  • TuxChartArea — sibling to TuxChartLine; fills the region under each line. Overlay (default, 0.22 opacity for multi-series read-through) and stacked variants. End-of-area labels colored to series. Pairs with TuxBigStat for the canonical "KPI strip over stacked area" composition (Charts UI Kit absorption carry-forward).
  • TuxChartScatter — correlation plot. Optional linear-regression trendline per series with R² readout in the legend. Variable point sizes for bubble- chart mode. The only chart in the family where both axes need labels (since x is no longer ordinal).
  • TuxChartGauge — 270° arc gauge for single-target metrics. Two variants: arc (needle
    • optional tone bands using status tokens) and progress (single filled arc, no needle). Use sparingly — research dashboards rarely need gauges; the showcase calls out when to reach for TuxBigStat instead.

Plus showcase routes under /visualizations/{chart-donut, chart-area, chart-scatter, chart-gauge}, full 6-step shipping discipline (nav, index cards, components.md rows, roadmap struck through).

The Priority B chart roadmap is now closed.

Added — next-sprint batch: SplitPane + AppSwitcher + ChartBar (2026-05-22)

Closes 3 of the 4 deferred roadmap candidates from the platform-aware sprint. Tauri-bindings companion doc lands too.

New components:

  • TuxSplitPane — in-page master-detail layout with resizable list pane (drag handle, double- click to collapse, sliver-click to expand) and optional bottom pane for related content. Width persists via localStorage when an id is provided. Folds to single-column below tablet. Selection is URL-bound (consumer's responsibility); empty-state defaults to TuxEmptyState if #empty slot not provided. Source: Fabric "Multiview" absorption.
  • TuxAppSwitcher — waffle- button trigger + popover grid of app tiles for hopping between TTI consumer apps (Landscape ↔ AI Studio ↔ future). Current-app tile carries aria-current="page" + visual disabled state. Source: Fabric "Suite header" absorption.
  • TuxChartBar — second Priority B chart, sibling to TuxChartLine. Native SVG; vertical (default) + horizontal orientations; single / grouped / stacked variants; optional comparison overlay (Snow "Projections vs Actuals" pattern); value labels above-bar or in-bar; auto SR summary. Follows chart-foundations.md doctrine.

New doc:

  • design/tauri-bindings.md — companion to platform-awareness. Enumerates which TUX components call which Tauri APIs (window controls in TuxAppFrame, share sheet in TuxArtifact, file dialog in TuxCodeBlock, notifications in TuxStatusToast), the web fallback for each, and a capability allowlist template for consumer Tauri shells.

Showcase routes: /components/{split-pane, app-switcher} + /visualizations/chart-bar — full 6-step shipping discipline (routes, nav, index cards, components.md rows, roadmap struck through).

Added — platform-aware Tauri build sprint (2026-05-22)

Doctrine + 8 components + 2 composables + 1 CSS utility + safe-area support landed in a single sprint. TUX shifted from web-only to Tauri desktop shells on Windows / macOS / Linux plus future Tauri Mobile (iOS / Android), which made the previously-skipped Apple / Material / Windows Figma kits legitimately useful.

Doctrine (grounds everything else):

  • design/platform-awareness.md — "TUX is one component tree, platform-adaptive at the chrome layer." Two-layer mental model (brand invariant, chrome adapts). 8 dimensions of variation (window controls, scrollbar, modifier keys, fonts, drag regions, menu bar, accent, share/file/notification APIs). Held 5 visual-language rejections: Liquid Glass as surface, Mica wholesale, Material tonal palette, elevation-as-color, SF Symbols icon family.

Foundations:

  • useTuxPlatform() — module-singleton composable that detects host OS + Tauri runtime
    • primary modifier + system accent post-hydration. Sets document.documentElement.dataset.platform so CSS branches without each component asking JS. SSR-safe defaults.
  • app/assets/css/tux-scrollbar.css — per-platform scrollbar styling auto-imported via globals.css. Mac overlay auto-hide, Win11 persistent slim (12px), Linux defers to GTK/Qt, web fallback. Recipes lifted from the scrollbar-kit absorption. Opt-out via .tux-scrollbar-default.
  • Safe-area-inset CSS on app/layouts/default.vue + app/layouts/sidebar.vue. Honors env(safe-area-inset-*) so Tauri Mobile builds get notch / home-indicator / gesture-nav clearance. Zero cost on desktop / web.

Chrome components (desktop / app-shell):

  • TuxAppFrame — custom Tauri titlebar with platform-correct controls. Mac: traffic lights top-left + title-toolbar unification. Win11: min/max/close top-right with native snap-layouts. Linux: close-only fallback. Slots: #left / #center / #right. Drag regions marked.
  • TuxFocusView — full- viewport overlay for inspecting one piece of content (a chart, a record). Back + title + actions chrome above a content slot. Esc + backdrop dismiss; focus trap; reduced-motion respected. Absorbed from Teams Stage view / Lightbox view.
  • TuxMenuBar — in-window File / Edit / View / Help strip for Win / Linux. Composes UDropdownMenu. Skipped on Mac (system menu wins).
  • TuxSplashScreen — branded app-launch overlay. Brand mark + maroon hairline + status. Bridges Tauri window-show → Vue hydrate gap.

Mobile components (Tauri Mobile target):

  • TuxTabBar — bottom-anchored 3-5 tab nav. Icon + label + maroon top-edge active rule (TUX signature). Safe-area-inset honored. 44px min tap target.
  • TuxFAB — Floating Action Button for the single "compose new" action per screen. Icon-only circle (56 / 40px) or extended pill with label.

Composables:

  • useTuxSwipe — pointer / touch swipe detection with directional callbacks. Accessibility rule: any swipe action MUST have a visible alternative.
  • useTuxRipple — Material- style tap-feedback ripple. Opt-in only. Honors prefers-reduced-motion.

Enhancements:

  • TuxKbd — reads from useTuxPlatform() instead of inline navigator-platform sniff. Renders Super on Linux + Win on Windows. Three platform symbol tables.
  • TuxModal — adds size prop (sm → 3xl) + variant: 'standard' | 'sheet' | 'auto'. Sheet variant is bottom-anchored with drag-handle pill (iOS / Material 3 mobile pattern). Auto picks sheet on iOS / Android.

Showcase routes (full 6-step ship discipline): /components/{app-frame, focus-view, menu-bar, splash-screen, tab-bar, fab, swipe, ripple} — all 8 added to app.vue nav, components index cards, design/components.md rows.

Platform-aware Figma absorption sweep (2026-05-22)

The doctrine pass was preceded by a 13-file absorption sweep across Apple / Google / Windows / cross-platform utility kits (Windows UI kit re-read with new lens, macOS 26 Tahoe, iOS 26 Liquid Glass, iOS+ iPadOS 26, macOS Browser, visionOS 26, Apple Widgets, SF Symbol Creator, Material 3, Android UI Kit, 50 Mobile Bottom Nav, Tailwind Headless UI Animations, Scrollbar Kit MacOS & Windows, UI Prep Layout Grids). 0 new components from absorption alone; recipes + roadmap candidates captured in per-file NOTES.md. 43/70 absorbed total.

Medium-signal absorption sweep (2026-05-22)

14 files cleared from the original medium bucket: 7 single-pattern (Calendar, Interactive Dropdown ×2, Progress Bar, Order list, Dashboard ×2) and 7 Microsoft-suite (M365, Fabric ×3, Store, Teams, SharePoint). 0 new components; 3 future-component candidates noted (TuxSplitPane, TuxAppSwitcher, TuxFocusView — the last shipped this sprint).

A multi-day absorption pass over the Figma project's high-signal reference systems (shadcn_ui Jan 2026, Vercel AI Elements, Primer Web, Microsoft Fluent 2 Web, plus the Windows UI kit and Aggie UX). Most findings confirmed TUX's architecture; the rest landed as new components, a layout, build-pipeline additions, and one infrastructure fix. Triage + per-file findings live under reference/figma-cache/; each NOTES.md records what was absorbed vs skipped vs deliberately deferred.

A follow-up medium-signal pass on 2026-05-21 absorbed five more files (Charts UI Kit, Data Visualization Graphs / Charts Kit, Data Table design components, Snow Dashboard UI Kit, Empty State Illustration Kit) — 0 new components, audit-only by design. Findings rolled into roadmap carry-forwards (design/roadmap.md) and one doctrine addition to TuxEmptyState / design/components.md ("no decorative illustrations").

A subsequent AI-studio chat-surface cluster pass on 2026-05-21 absorbed four more files (Chat UI kit, Chat Input Box, ChatGPT UI Kit, MCP Apps for Claude). Net new: 0 components shipped. The chat surface was already saturated by the Vercel AI Elements pack + Fluent 2 + Primer work. Most absorption value was confirming TuxComposer #toolbar-extra

  • TuxChatMessage #header-trailing cover the canonical shapes; the exception was Anthropic's MCP Apps kit which mapped a three-tier display taxonomy (inline card → inline carousel → full screen) that will guide a future TuxMcpEmbed + TuxCardCarousel build when tti-ai-studio integrates MCP tool output. Findings + carry-forwards in reference/figma-cache/mcp-apps-for-claude/NOTES.md and the roadmap.

Added — roadmap clear: small wrappers + pagination + first chart + a11y docs (2026-05-21)

Big batch closing the Priority B/C/D items already on the roadmap before this session started. Net new: 8 components shipped + 4 accessibility doc pages + TuxTOC wiring + TuxStatComparison demo on research-landing. Lint clean throughout.

Easy wrappers (Nuxt UI primitives + TUX chrome):

  • TuxTabs — wraps UTabs. 2px maroon active underline replaces the default accent. intent="bold" swaps to uppercase tracked labels for eyebrow-rhythm contexts. orientation="vertical" covers settings panels. Closes both TuxTabsHorizontal and TuxTabsVertical roadmap entries (one component, prop covers both).
  • TuxTooltip — wraps UTooltip. Optional title + hairline rule; tuned max-width (~22ch); shortcut glyphs via kbds. For richer panels, consumers use TuxTeachingPopover (onboarding) or UPopover directly.

Pagination + result-display family:

  • TuxResultCount — "Showing 1–24 of 412 corridors · 24 per page." v-model bound page
    • pageSize; page-size picker auto-snaps to page 1 on change. Naive pluralization with nounPlural override.
  • TuxLoadMore — explicit button with remaining count below + terminal divider on completion (loaded >= total). SEO-friendly middle ground between paginated and infinite.
  • TuxInfiniteScroll — IntersectionObserver sentinel with throttled emit + loading state. Honors prefers-reduced-motion automatically (renders explicit button instead of auto-fetching); keyboardFallback prop forces the button on for accessibility-critical surfaces.

Feedback + comparison:

  • TuxReactionBar — light-touch acknowledgement strip. Default trio (helpful · question · disagree) with full configurability via reactions prop. Counts display-only; v-modeled active reactions. Two sizes (sm / md).
  • TuxStatComparison — before/after stat block with delta + tone. Three polarities (direct / invert / neutral) for metrics where down-is-good (latency, error rate). Three layouts (row / stack / inline). Demonstrated alongside the existing factoid row in /examples/research-landing.

First Priority B chart:

  • TuxChartLine (~520 LOC) — native SVG line chart following the chart-foundations doctrine. End-of-line value labels colored to series (default, not opt-in — the accessibility win flagged by the Data Viz Graphs absorption). Optional series[].previous for dashed prior-period overlay, optional series[].band for confidence bands, optional markers, auto-derived screen-reader summary. Showcase at /visualizations/chart-line with five examples including a TuxChartFrame-wrapped editorial variant.

Accessibility documentation (4 pages):

Existing accessibility.vue moved to accessibility/index.vue with a new "deep-dive references" section linking to all four sub-pages via TuxCard tiles.

Polish queue items:

  • TuxTOC wired into TuxReportWebFrame #toc slot in /reports/web-frame showcase. Uses TuxTOC's auto-detect (scans h2/h3 in the article) so the IntersectionObserver scroll-spy works without rebuilding the items array. The fallback (plain anchor list from the frame's toc prop) still renders when the slot is empty.
  • research-landing example refreshed with a TuxStatComparison year-over-year delta row beneath the by-the-numbers factoid. One of three uses inverted polarity to show non-compliance going down = success.

Added — adoption batch from absorption carry-forwards (2026-05-21)

Shipping the small, broadly-useful improvements identified across the last three absorption passes. No speculative builds — each item had multiple absorption files asking for it.

Tier 1 — slot + prop additions (≤30 LOC each):

  • TuxComposercancelable prop + cancelLabel prop + @cancel emit. When the composer is wrapped in a modal / slideover / inline edit, surfaces an explicit [Cancel] [Send] pair. Cancel is ghost-intent; Send stays primary. Absorbed from the Chat Input Box Figma file. New showcase section on /components/composer.
  • TuxRichDataGrid#headerMenu per-column slot. Consumer-supplied dropdown attached to each column header for sort / hide / pin / filter actions. Slot scope: { column, sortKey, sortDir }. No default chrome — consumers compose with UDropdownMenu, UPopover, or a full TuxFilterPanel. Absorbed from the Data-table-design Figma kit + Snow Dashboard. New showcase section on /components/rich-data-grid.
  • app/layouts/sidebar.vue#aside slot for an optional right rail. Fixed-width (asideWidth prop, default 18rem), sticky-scrolling next to the main panel, hidden under md viewport. Absorbed from Snow Dashboard's notifications + activities rail. The landscape-dashboard example refactored to use this slot (its Recent-activity + Active- agents tiles moved out of the page body into the layout aside — they now stay sticky while main content scrolls).

Tier 2 — Conventions docs (design/components.md):

  • First-run AI surfaces — Examples / Capabilities / Limitations. ChatGPT's canonical three-column taxonomy for first-run splashes. Compose with TuxFactoid density="3" or 3-up TuxCard grid; no new component needed.
  • MCP tool output — inline card / inline carousel / full screen. Three-tier display decision tree for MCP tool results. Maps each tier to a TUX composition (TuxArtifactTuxCardCarouselTuxArtifact inside TuxSlideover/TuxModal).

Tier 2 — new design doc:

  • design/chart-foundations.md skeleton. Captures the doctrine future TuxChartLine / TuxChartBar / TuxChartArea will follow — palette rules, in-bar vs above value labels, end-of-line label default, comparison-series patterns, brush/range selector, KPI strip composition, accessibility. Lands before the components so they ship in line with the doctrine.

Tier 3 — new component:

  • TuxCardCarousel — horizontal scroll of cards with editorial chrome (eyebrow + display- face title + signature rule + arrows + optional pagination dots). Thin wrapper around UCarousel (embla under the hood); cards are the consumer's responsibility — TuxCard, TuxArtifact, TuxContactCard, or a bare <article> all work. Two render modes: items array + #item scoped slot, or direct slide blocks via the default slot. Re-opens the carousel question we deferred in the shadcn absorption — MCP-Apps-for-Claude (inline carousel tier) was the trigger consumer. Full 6-step integration: source + showcase at /components/card-carousel
    • nav entry + index card + components-md row + CHANGELOG.

Changed — PECAN → Landscape rebrand (2026-05-21)

The downstream sensitive-data classifier consumer formerly named PECAN is now Landscape. The rebrand touched ~50 files across TUX; historical record (this CHANGELOG file and the docs/adr/*.md records) was deliberately left intact — entries reference the project under its name at the time the entry was written.

  • Renamed app/pages/examples/pecan-dashboard.vuelandscape-dashboard.vue. Route is now /examples/landscape-dashboard.
  • Renamed public/kits/pecan/public/kits/landscape/; kit slug + label updated in app/pages/kits.vue.
  • Prose updated across README.md, design/components.md, design/roadmap.md, ~30 showcase pages under app/pages/, 10 component JSDoc blocks (TuxDescriptionList, TuxCodeBlock, TuxFilterPanel, TuxPagination, TuxTeachingPopover, TuxTreemap, TuxSearch, TuxSiteNav, TuxRichDataGrid, TuxFooter), top-of-file comments in nuxt.config.ts / app/app.vue / app/layouts/sidebar.vue, and the public/kits/aggieux/*.jsx reference catalog (default productName knobs on banners, footer demo strings, breadcrumbs).
  • First-mention-per-file uses "Landscape (formerly PECAN)" for continuity; later mentions are bare "Landscape."
  • Refreshed app/pages/examples/landscape-dashboard.vue to use the full sidebar layout + KPI row with delta-vs-previous readings + inline ingest-rate sparkline + two-column main/right- rail grid with Recent-activity + Active-agents tiles. Composition reference for the patterns absorbed from the Snow Dashboard (NOTES). Composes ~15 Tux* + Nuxt UI 4 primitives. No new components — the right rail is a host-driven grid composition, not a layout slot (the #aside slot follow-up on sidebar.vue waits until a second consumer surface needs it).

Changed — empty-state stance documented

  • TuxEmptyState — JSDoc now states the deliberate "no decorative illustrations" stance. Companion entry added to design/components.md Conventions section. Reaffirmed against the Empty State Illustration Kit absorption (NOTES); the five kind presets remain the canonical taxonomy.

Added — AI-elements component pack (Vercel AI Elements absorption)

Five new components and one enhancement, all driven by patterns from Vercel's AI Elements that nothing in TUX or Nuxt UI 4 covered. Together they let tti-ai-studio surface budget visibility, response alternatives, inline-grounded citations, generated artifacts, and follow-up suggestions — the AI-specific affordances real consumer apps need but TUX previously left to ad-hoc composition.

  • TuxSuggestionChips — horizontal row of clickable prompt-suggestion chips. Empty-state composer or post-response follow-ups. Accepts plain strings or { label, prompt } for short-chip / longer-prompt cases.
  • TuxBranchNav‹ N of M › navigator for response alternatives. v-modeled 1-indexed position, hideSingleton defaults true (N=1 renders nothing), optional loop. Position rendered as text for SR announcement.
  • TuxInlineCitation — academic-style superscripted [N] pill in body text. Hover or click reveals UPopover with title + URL + excerpt + score. One source per pill (not Vercel's "+N" aggregation); composes with the existing TuxCitations footer list.
  • TuxContextMeter — token-utilization meter. Compact pill with conic-gradient ring + % used; popover reveals input/output token counts + per-side cost + total. Tone-codes ok / warn / alert at 60% / 85% thresholds — visible signal as long sessions approach the context limit.
  • TuxArtifact — structured container for AI-generated output (code file, dataset, document, image). Header (icon + title + meta + actions row) + body slot. Common actions (copy / download / regenerate / share) via the actions prop emit named events; custom actions via #actions slot. Standalone — consumer wraps in container (inline, sidebar aside, full-page).
  • TuxCodeBlock gained a download button beside the existing copy. Resolves filename from a downloadName override → the filename basename → code.{ext} from a 25-language extension map. Both buttons share a hover-revealed __actions container.
  • TuxChatMessage gained a #header-trailing slot for right-aligned controls (canonical use: TuxBranchNav; also fits per-message model pickers, regenerate buttons). Surfaced by dogfooding the refactor of the example page; previously the header had no place for trailing controls.

Added — TuxRemovableChip (Primer Token absorption)

Standalone primitive for interactive dismissible pills. Distinct from TuxBadge (decorative) — this is button-shaped, focusable, and emits events: click-toggleable via selected, removable via the × button (or clickToRemove for whole-pill click). Three sizes, optional leading icon, optional disabled state. Pattern was previously hand-rolled inside TuxFilterPanel; extraction gives a primitive for tag inputs, recipient lists, multi-select chips, etc.

  • TuxFilterPanel refactored to consume TuxRemovableChip for applied-filter pills at the top of the panel. Removes ~30 LOC of hand-rolled chip markup and scoped styles; behavior preserved (whole-pill click removes, × rendered as decorative affordance).

Added — TuxInfoLabel, TuxTeachingPopover (Fluent 2 absorption)

Two small patterns from Microsoft Fluent 2 Web. Both deferred from the initial Fluent pass until the integration sweep landed them with showcase routes and convention.

  • TuxInfoLabel — form-field label with an (i) info button. Hover or click reveals a popover with extended explanation. For technical research forms where field meanings deserve context (ITAR rubrics, retention classes, classifier metrics). Pairs with form primitives via the standard for= attribute; optional required shows the maroon *.
  • TuxTeachingPopover — onboarding / guided-tour tooltip. Distinguished from UTooltip (passive hover help) by the guided-flow affordances: optional header image, body, Next / Skip footer, step counter ({step} of {totalSteps}). v-models both open state and current step. onBrand variant for high-emphasis announcements.

Added — Sidebar app-shell layout + demo example

  • app/layouts/sidebar.vue rewritten to compose Nuxt UI 4's UDashboardGroup + UDashboardSidebar + UDashboardPanel. Earlier scaffold was hand-rolled CSS; the rewrite drops ~70 LOC and inherits collapse-to-icons, mobile slideover, resizable handle, and cookie-backed persistence from Nuxt UI. Opt in per page via definePageMeta({ layout: "sidebar" }). Slot surface: #header (top bar / breadcrumbs), #rail-header (brand), #rail (nav body), #rail-footer (user), default (main content). Rail slots receive { collapsed, collapse } scope vars.
  • /examples/sidebar-shell — new composition example demonstrating the layout's slot surface. Sample app-shell with TTI brand, hierarchical UNavigationMenu in the rail, user-account footer, and a content surface with factoids + recent-activity list.

Added — LaTeX math in MDC pipeline

remark-math + rehype-katex wired into nuxt.config.ts under mdc.remarkPlugins / mdc.rehypePlugins. KaTeX CSS imported globally in globals.css. Markdown sources can now use $inline$ and $$display$$ math; rendered HTML includes both katex-html (visual) and katex-mathml (a11y) trees. The /markdown demo gains a Math section exercising both forms. Bundle-size cost: ~75 KB KaTeX CSS loads on every page (lightweight enough to justify; consumers can swap to a manual per-page import if they want to be strictly lazy).

Added — Breakpoint tokens (Windows UI kit absorption)

  • design/tokens.json gained a breakpoint block: xs (0) / sm (640) / md (768) / lg (1024) / xl (1280) / 2xl (1536), with $description per breakpoint documenting the semantic trigger (what surface state changes at this width). Values match Tailwind v4 defaults so md: / lg: utilities keep their intuitive meaning; the token source is the durable reference. Pulled from a survey of Microsoft's Windows UI kit responsive ladder (348 / 660 / 708 / 1020 / 1252), rebased to TUX's editorial-first content widths.
  • globals.css @theme block gained --breakpoint-* entries mirroring the token source.

Added — TuxEmptyState compact prop + kind preset library

Two enhancements to the empty-state component, one from the Primer pass and one from the Backstage pass:

  • compact boolean prop (Primer pass) — reduces icon, heading, description, and padding sizes for narrow-column placements (facet results, inline list area, sidebar widget). Confirmed against Primer's Blankslate small variants and again against Backstage's Empty Card use case.
  • kind preset prop (Backstage pass) — five named cases that pre-fill icon + title + description for the most-reused empty-state shapes:
    • no-data — empty dataset, nothing created yet
    • no-results — query / filter returned nothing
    • not-found — resource doesn't exist (or moved)
    • no-permissions — auth-gated; user lacks access
    • first-run — onboarding; "welcome, get started"

    Explicit icon / title / description props still win per-field, so consumers can use a preset as the baseline and override the voice. Surveyed from Backstage's seven dedicated empty-state pages (Empty Card / Table / Page / No Builds to Show / Entity Not Found / Create a Component); collapsed to the five most-reused shapes for TUX. Showcase at /components/empty-state demonstrates each preset.
  • Template dedup as a side-effect. The earlier compact addition only worked on the with-card branch — the no-card branch had a duplicate template that drifted. Restructured to one template using <component :is="noCard ? 'div' : 'TuxCard'"> (made possible by the global Tux component registration this session also shipped); both branches now honor compact and kind identically.

Added — Standard composition conventions

  • design/components.md gained a new Conventions section with two documented standards:
    • Chat-message actions (Vercel AI Elements absorption) — the standard TuxChatMessage #tools icon set: Copy · Regenerate · Share · Helpful · Off in that order. Icon names, labels, and expected emit-event names all listed so consumers don't reinvent.
    • Form validation — when to use which (Backstage absorption) — decision tree for four placements: inline field error / inline form summary / blocking dialog / page-or-session banner / toast. Each placement names the components, the use case, and the behavior expected. Anti-patterns called out explicitly (modal for a single bad field, banner for a single bad field, etc.). Backstage's 38-frame form-validation page surfaced the gap — TUX leans on UFormField + TuxAlert + UModal + useToast() for primitives but previously didn't document the decision.

Added — Figma absorption cache + scripts

reference/figma-cache/ houses the durable artifacts from the absorption pass: per-file NOTES.md (skip / absorb / tension / decisions framework) with cover thumbnails for all 70 files in the source project. Aggregator at INDEX.md. Helper scripts at _scripts/sync.py + rebuild-index.py re-sync from the Figma REST API (rate-limit-aware) and regenerate the index from on-disk state.

Fixed — MDC component resolution

Nuxt's default component auto-import works via compile-time template rewrites; components are not registered via app.component(). That made every Tux* invisible to MDCRenderer's runtime resolveComponent() call, so markdown like ::tux-alert{...} / ::tux-callout{...} rendered as plain text and logged [Vue warn]: Failed to resolve component: TuxAlert on every render.

  • nuxt.config.ts now sets components: [{ path: resolve(layerDir, "app/components"), global: true }]. global: true registers each component via app.component() at boot so runtime resolvers find them. Bundle cost: Tux* components ship in the main chunk instead of being lazy-loaded — acceptable because they're used across virtually every page anyway, and the win (markdown surfaces can use any ::tux-* block without explicit registration) is real. Propagates to consumer apps (PECAN, tti-ai-studio) when they extend this layer.

Fixed — Typesafe emit-union narrowing

vue-tsc flagged two new components where emit(union) couldn't narrow to a specific literal:

  • TuxArtifact — the actions prop's loop called emit(a) where a: ArtifactAction. Replaced with an onAction(a) switch dispatch over the literals.
  • TuxBranchNavemit(delta === 1 ? "next" : "prev") couldn't narrow. Replaced with if (delta === 1) emit("next"); else emit("prev");.

Changed — tti-ai-studio-session example uses TuxChatMessage

The example page previously used hand-rolled <article> blocks for both message turns. Refactored to use TuxChatMessage — closes a dead-code-audit finding (the component had no consumer outside its own showcase route) and demonstrates the canonical pattern. The user turn becomes a 3-line call; the assistant turn uses four slots (#header-trailing for TuxBranchNav, default for body, #citations for the footer list, #tools for the standard action set).

Changed — app/pages/examples/tti-ai-studio-session.vue composition

Beyond the TuxChatMessage refactor, the example was previously heavily updated this session to demonstrate the new AI-elements components in their natural composition: TuxContextMeter in the page header, TuxBranchNav in the assistant header, TuxInlineCitation inline in the prose, TuxArtifact wrapping a TuxCodeBlock after the message, TuxSuggestionChips before the composer, plus UChatReasoning / UChatTool / UChatShimmer from the Nuxt UI 4 Chat suite to show how the two layers compose.

1.4.2 — 2026-05-14

Patch: align the HC theme's primary palette steps with the brand value stated in ADR-0005. The HC palette was sampled from TTI's 508- accessible PPTX where the maroon anchor is #500000, but --color-maroon-400 and --color-maroon-500 were inheriting from :root (#b14a6c raspberry and #5c0025 deep wine respectively), which leaked a one-color-step deviation into any Nuxt UI primitive under tti-hc. No visual impact on tti or tti-dark.

Fixed — HC primary palette aligned with ADR-0005

  • [data-theme="tti-hc"] in app/assets/css/tokens.css now pins both --color-maroon-400 and --color-maroon-500 to #500000. The 500 override is the visible fix — solid Nuxt UI primitives (UButton, UBadge, etc.) in HC mode were rendering #5c0025 from :root instead of the ADR-stated #500000. The 400 override is defensive (Nuxt UI's .dark path doesn't fire under HC, so 400 isn't read in practice) but keeps the "three blocks, each a full override of every semantic token" discipline from ADR-0005 intact.
  • Pure values throughout, in keeping with HC's "zero color-mixing" principle.

1.4.1 — 2026-05-14

Patch: bridge @nuxtjs/color-mode to Nuxt UI's .dark class so Nuxt UI primitives actually inherit the dark theme. Completes v1.4.0's dark-accent retune — without this bridge, <UButton> / <UInput> / <UBadge> / <USelect> stayed in light mode under tti-dark because Nuxt UI's runtime CSS rule is gated on .dark, but our color-mode is configured with classSuffix: "" so it emits tti-dark, not .dark.

Fixed — Nuxt UI primitives in dark theme

  • app/plugins/nuxt-ui-dark-bridge.client.ts (view) watches colorMode.value and toggles .dark on <html> when the preference is tti-dark. tti-hc is light-surfaced per ADR-0005 so it intentionally stays without .dark (Nuxt UI's light path is correct for HC).
  • Brief FOUC trade-off: the boot script sets [data-theme] + the theme class before Vue hydrates, but this Vue plugin only runs on hydration. On a fresh load in tti-dark, primitives flash light briefly before this plugin adds .dark. The surrounding page is already dark from tokens.css so the flash should be imperceptible; the harder fix (patching the boot script to add both classes at once) is parked for now.

Changed — --color-maroon-400 consolidated onto the dark-mode teal accent

With the bridge plugin firing .dark, Nuxt UI's text-inverted flips from white to stone-900 in dark mode, which changes the calculus for the primary-button fill (the button now carries dark text on top, conventional inverted-button pattern).

  • --color-maroon-400 in [data-theme="tti-dark"] retuned from brick-wine #702C28 to lifted TTI teal #6BB4C0 — the same value as --brand-primary. Consolidates the dark-mode accent system on a single hue: maroon stays anchored on light surfaces and in --brand-fill for marketing panels, but every primary-colored control in dark mode reads as TTI teal. Black-on-#6BB4C0 ≈ 7.9:1 AAA.
  • Earlier wine candidates (#b14a6c, #7a1233, #702C28) all rang awkwardly next to the teal accent — either too pink (high saturation) or too dim (deep brown against a near-black sidebar). Picking teal unifies the dark-mode accent story.

1.4.0 — 2026-05-14

Nuxt UI gap-analysis batch (five new components covering kbd hints, prose typography, hierarchical lists, and edge-anchored drawers, plus a global keyboard-shortcut layer), the Batch J visual-language application sweep, and a self-hosted fonts migration. No breaking API changes — pin bump from #v1.3.0 to #v1.4.0 is additive only.

Changed — self-hosted fonts via @fontsource/* + scripts/sync-fonts.mjs

Replaces the @nuxt/fonts module (Google-provider auto-fetch) with explicit @font-face declarations in app/assets/css/fonts.css, sourced from @fontsource/* npm packages and synced into public/fonts/<family>/*.woff2 via scripts/sync-fonts.mjs. Air-gapped-build clean — no CDN dependency at runtime or build time, which matters for state-agency deploys and consumer apps that ship behind enterprise proxies.

  • @fontsource/{open-sans,oswald,work-sans,public-sans,jetbrains-mono} added as devDependencies. Re-sync with npm run sync:fonts.
  • app/assets/css/fonts.css declares every face/weight/style combination the four-family rule uses; loaded first in the CSS cascade so subsequent rules can reference the families.
  • nuxt.config.ts drops @nuxt/fonts from modules and its fonts block; prepends fonts.css to the css array.

Changed — dark theme brand accent retuned to lifted-TTI-teal

Dark theme's --brand-primary switches from lightened maroon (#e795a8) to lifted TTI teal (#6BB4C0 — the tertiary swatch from design/palette.md, lifted for dark-page contrast). Earlier values read too pink in the studio where most accent points live on small chrome elements (composer border, focus rings, active-tab text); teal reads more institutional at small sizes.

  • --brand-primary / --brand-primary-deep retuned in [data-theme="tti-dark"] to #6BB4C0 / #5B9CA8. Clears AAA on every dark surface (7.1:1 on raised, ~8.2:1 on page, ~8.7:1 on sunken).
  • --color-maroon-400 = #702C28 override added — Nuxt UI v4's solid-variant button fill uses primary-400 in dark themes; the unmodified palette value (#b14a6c) reads as raspberry pink, and an intermediate #7a1233 still rang loud against the teal accent (mid-saturation magenta hue). Reuses the existing --map-seq-maroon-2 value — brick-wine, low saturation, brown-leaning — so the button recedes into the dark page surface. Edge contrast is ~1.9:1 vs page (below WCAG 1.4.11's 3:1 UI threshold by design); readability comes from the white label (clears 10:1 on this fill) and the button's shape / hover state, not from surface contrast.
  • --text-brand repointed to the new teal-deep so brand-tinted inline text stays coherent with the accent.

Maroon brand presence preserved via --brand-fill (always #5C0025) on marketing panels, the --color-maroon-400 override on solid buttons, and --chart-1 pinned to a wine-leaning value so the brand chart series doesn't silently shift to teal.

Added — TuxSlideover (edge-anchored drawer)

Slice 4 (final) of the Nuxt UI gap-analysis follow-through. Distinct affordance from TuxModalTuxSlideover slides in from a viewport edge and preserves the reading context behind the scrim. Use for row detail (click a TuxRichDataGrid row → slide in field metadata), filter panels, mobile nav reveals, and bottom action sheets.

  • TuxSlideover (app/components/TuxSlideover.vue, /components/slideover) — edge-anchored panel built on the native <dialog> element so focus trap, escape handling, scrim rendering, and ARIA semantics come from the platform. Slide animation rides Batch J's --ease-corridor curve over --motion-base duration. The panel itself opts into data-tux-overlay (Batch J easing) and data-tux-elevation="overlay" (Batch J shadow tier) so it inherits the system's anchored-surface rhythm without duplicate CSS.
  • Three sides
    • right (default) — row detail, filter panel. 28rem wide, full viewport height, rounded on the left edge.
    • left — mobile nav drawer. Mirror of right.
    • bottom — action sheet. Full viewport width, 24rem tall, rounded on the top edge.
  • Props
    • v-model: boolean — open / closed
    • side: "left" | "right" | "bottom" (default "right")
    • size: string — CSS length for width (left/right) or height (bottom)
    • title + eyebrow — header heading + optional eyebrow above
    • showClose: boolean — top-right close button (default true)
    • closeOnBackdrop: boolean — dismiss on scrim click (default true)
  • Slots — default (body), #header (replace eyebrow + title region), #footer (action row with sunken-surface tint).
  • Reduced motion@media (prefers-reduced-motion: reduce) skips the slide animation; the panel appears in place.
  • Doctrine note — added "Slideover vs Modal" section to the demo page: choose slideover when the user is drilling into the page they're on (row detail, filter panel, side-by-side editor); choose modal when the underlying page doesn't matter (confirmation, fatal error, full-attention form).

Added — TuxTree (hierarchical list)

Slice 3 of the Nuxt UI gap-analysis follow-through. Fills the sitemap / corpus / BI-dataset-explorer gap identified in the Nuxt UI audit. Native (not a UTree wrapper) since the visual is brand-specific and the keyboard semantics are simple enough to own.

  • TuxTree (app/components/TuxTree.vue) — recursive hierarchical list. Each node carries { id, label, icon?, description?, mono?, to?, href?, badge?, children? }. Pass to for internal nav (renders <NuxtLink>), href for external, or leave both off for a pure toggle/select row (renders <div role="button" tabindex="0"> to sidestep Tailwind v4's @layer base button reset, which would otherwise win over scoped CSS via layer cascade order). Set mono: true on a node to render its label in JetBrains Mono — useful for path / id / hash leaf rows.
  • TuxTreeNode (app/components/TuxTreeNode.vue) — single-row recursive child component. Owns the row chrome (chevron, icon, label-block, badge) and indents by depth via padding-left. Expand state lives in the parent tree; this component just queries it via a callback prop.
  • Props (TuxTree)
    • items: TreeItem[] (required)
    • defaultExpanded?: string[] — initial open IDs (defaults to every root node)
    • storageKey?: string — sessionStorage key for collapse persistence
    • showGuides: boolean — 1px sand guide lines under expanded branches (default true)
    • v-model:selected — currently-selected node ID
    • ariaLabel: string — passed to [role="tree"]
    • Exposes expandAll() / collapseAll() via template ref
  • Visual rhythm — maroon chevron + maroon label tint on the selected row (anchored by a 2px maroon left bar, mirroring .tux-cmd__item--active so cross-component selection reads consistently). Sand guide lines under expanded branches help deep hierarchies stay scan-able. Container query (@container tux-tree (max-width: 280px)) drops the description line and tightens padding at narrow widths — per CLAUDE.md rule 1.
  • Keyboard navEnter / Space toggle+select, ArrowRight expands a collapsed branch, ArrowLeft collapses an expanded branch. Up/down navigation uses native focus order through the row elements (which are all focusable).
  • Demo page /components/tree shows three example trees: sitemap (route links + badges), corpus / filesystem browser (mono leaf labels, row-count badges), and a BI dataset hierarchy (per ADR-0009) with v-model:selected driving a focused-field side panel.
  • Nav wiringapp/app.vue sidebar, app/pages/components/index.vue catalog, and design/components.md doctrine table.

Added — TuxProse, /typography refresh, doc-page surround nav

Slice 2 of the Nuxt UI gap-analysis follow-through. Consolidates the prose typography that was duplicated across three pages, refreshes the /typography foundation page to reflect the four-family rule from design/tux.md, and adds a prev/next surround at the bottom of every /design/[doc] page.

  • TuxProse (app/components/TuxProse.vue, /components/prose) — typographic shell for long-form markdown. Single :deep() block covering H1/H2/H3/H4/p/ul/ol/li/ strong/em/code/pre/a/table/th/td/hr/blockquote/img. Default wrapper is <article>; pass as="div" when the parent already provides the landmark. Replaces three duplicate ~130-line .prose-tux :deep(…) blocks that lived inline in app/pages/changelog.vue, app/pages/design/[doc].vue, and app/pages/markdown.vue (~400 lines of duplication eliminated).
  • [/design/doc](app/pages/design/[doc].vue) rewrite — wraps <MDCRenderer> in <TuxProse> and adds a Nuxt-UI-style ContentSurround at article bottom (prev / next sibling design docs, sorted by slug). The surround cards opt into Batch J's data-tux-elevation="rest" for consistent shadow tier.
  • /changelog — wraps <MDCRenderer> in <TuxProse>. Drops the 90-line scoped prose CSS block.
  • /markdown — wraps the MDC preview in <TuxProse as="div"> so the prose rhythm renders inside the card without doubling up the <article> landmark. Drops the 110-line scoped prose CSS block.
  • /typography refresh — dek now opens with "Four families, three style variants, one mono" instead of the stale Public Sans / JetBrains Mono framing. Adds:
    • Family roster — five articles, one per token (--font-body, --font-display, --font-bold, --font-elegant, --font-mono), each showing role + sample. Each card opts into Batch J's data-tux-elevation="rest".
    • heading--elegant section with both the upright and italic forms (the page mentioned the utility before but never showed it).
    • Style variants triptych — three side-by-side cards demonstrating default / .style--bold / .style--elegant applied to the same heading text.
    • TuxProse wrapper demo — renders a sample H1 / p / H2 / list / blockquote stack inside the wrapper to make the prose rhythm legible to designers landing on the foundation page.
    • Cross-links to /design/tux, /tokens, /components/kbd, /style-variants, and /changelog.
  • Nav wiringapp/app.vue sidebar, app/pages/components/index.vue catalog, and design/components.md doctrine table all gain the TuxProse row.

Added — TuxKbd, TuxShortcutsHelp, global defineShortcuts

Slice 1 of the Nuxt UI gap-analysis follow-through. Consolidates the three hand-rolled <kbd> styling blocks that lived inside TuxCommandPalette into one component, leans on Nuxt UI's defineShortcuts for platform-correct hotkey wiring, and mounts a global command palette + shortcuts-help overlay at the shell so every page benefits from ⌘K, /, and ?.

  • TuxKbd (app/components/TuxKbd.vue, /components/kbd) — token-styled <kbd> with Mac vs PC modifier normalization (post-hydration to avoid SSR mismatch). Three sizes (xs/sm/lg), single-key + combo + sequence forms, built-in glyphs for arrows / enter / escape / tab / space, default slot for custom content (function keys, icons). Uses the same defineShortcuts key grammar (meta, ctrl, shift, …) so a binding declaration and its rendered hint share a vocabulary.
  • TuxShortcutsHelp (app/components/TuxShortcutsHelp.vue) — modal overlay listing every wired shortcut, grouped. Auto-classifies items as combo (modifier+key, rendered as one TuxKbd group) or sequence (no modifier, rendered as TuxKbd "then" TuxKbd). Built on <dialog> for free focus trap + scrim + escape semantics, same anatomy as TuxCommandPalette. Opt-in data-tux-overlay + data-tux-elevation="overlay" so it picks up the Batch J easing curve and shadow.
  • TuxCommandPalette refactor — replaces the hand-rolled keydown listener with defineShortcuts({ meta_k: …, usingInput: true }) for platform-correct meta/ctrl handling. Replaces the inline <kbd> markup (esc hint, per-item shortcut, footer hints) with <TuxKbd>. Drops the now-redundant .tux-cmd__esc-hint, .tux-cmd__item-shortcut, and .tux-cmd__footer-hint kbd scoped CSS blocks. Arrow / enter / escape stay on the dialog's local input handler since they're list-nav, not app-level shortcuts.
  • Shell-level wiring in app/app.vue — mounts one <TuxCommandPalette> and one <TuxShortcutsHelp> globally, with paletteGroups derived from the existing navTree so a single source of truth drives sidebar nav and palette search. defineShortcuts at the shell wires:
    • meta_k — open the global palette (handled by TuxCommandPalette internally)
    • / — open the global palette (GitHub idiom)
    • ? — toggle the shortcuts-help overlay
    • g-c / g-t / g-d / g-h — sequence shortcuts to /components, /tokens, /design/tux, /
  • /components/command-palette demo — adds :disable-hotkey="true" on the page's local palette instance so ⌘K only fires the global one. Replaces the page's hand-rolled <kbd> markup with <TuxKbd>.
  • Nav wiringapp/app.vue sidebar tree, app/pages/components/index.vue catalog table, and design/components.md doctrine table all gain the TuxKbd row.

Added — Batch J: visual-language application sweep

A surface-level pass that applies the Batch E-prelude tokens (focus, easing, elevation, identity primitives) across the existing kit without touching every component file. Pure CSS, additive, opt-in via attribute selectors and utility classes. Imported from the standalone tti-ux-design skill payload (2026-05-14), where the doctrine and implementation were authored alongside.

  • app/assets/css/tux.css gains a BATCH J section with six rules:
    • J.1transition-timing-function: var(--ease-survey) default for button, a, input, select, textarea, [role="button"], [role="tab"], [role="option"], .tux-card, .card-linked* (gated by prefers-reduced-motion). Opt-in data-tux-overlay--ease-corridor; data-tux-arrival--ease-arrival. Inline JS transitions and component-declared curves still win.
    • J.2[data-tux-elevation="flat|rest|hover|overlay|pinned"] maps the five elevation tiers to a single attribute. The hover value also applies translateY(-1px) for the canonical lift.
    • J.3.tux-hoverlift utility: one-class opt-in for the rest → hover transition pairing.
    • J.4.tux-mark / .tux-mark--lg / .tux-mark--xl for sizing and tinting identity-primitive <symbol>s from /identity-primitives.svg (already shipping in public/). Default 16×16, currentColor = --brand-primary; --mark-size and --mark-color are the per-instance hooks.
    • J.5.tux-section-divider: eyebrow label + maroon-to- transparent gradient rule packaged as a one-class utility.
    • J.6[data-tux-rowgrid]: subtle 6%-maroon repeating-vertical- lines background, opt-in on heroes / empty-state panels / section breaks.
  • design/visual-language-evolution.md gains the matching Batch J — 2026-05 section documenting each rule, the opt-in pattern, and the Nuxt-vs-skill path note (assets/identity-primitives.svg in the skill payload → /identity-primitives.svg here).
  • No new tokens, no new components, no JS changes. Batch J consumes the prelude tokens (--ease-survey/corridor/arrival, --motion-base/ fast, --elevation-flat/rest/hover/overlay/pinned, etc.) already in app/assets/css/tokens.css. The mapping --font-body-bold--font-bold reconciles the skill payload's token name with the four-family typography rule.

Added — public/kits/aggieux/ major expansion

Refresh of the TTI-curated AggieUX preview catalog at public/kits/aggieux/ from the skill payload (ui_kits/aggieux/). Not a change to reference/aggieux/v2.0.1/ (the frozen upstream CDN snapshot at aux.tamu.edu, untouched per CLAUDE.md).

  • 43 new Aggie*.jsx demo pages covering chart foundations, charts/maps/treatments/viz, data tables, descriptions, directory, forms (core + advanced + field grid + date range + dropdown- combobox), filters, status states, banners, sectioning, templates, hover/focus disclosures, accessibility, image lightbox, load-more, feedback, toggle/slider, toolbar, transfer, tree, rate, rich data grid, sidebar banner, specialized patterns, AI-studio surface, corridor strip, collab, descriptions, guidance, map legend, news contact (sectioning, etc.). Net 23 → 66 files in the kit folder.
  • 10 updated pagesAggieCatalog (registers every new section), AggieButtonsAlerts, AggieCards, AggieChrome, AggieIconLink, AggieMenus, AggieNewsContact, AggiePageHeaders, AggiePages, App.jsx.
  • index.html updated to register all 64 JSX modules via <script type="text/babel"> tags.
  • AggieDisclosure.jsx (Batch 10: accordions / publication accordion / Q&A) and AggieDisclosures.jsx (Batch A.1: tooltips and popovers) are intentionally distinct — both ship.

Added — StructuredOutput.jsx for tti-ai-chat kit

Imported from the skill payload's tti-ai-studio kit (collapsed-naming mirror of our tti-ai-chat).

  • public/kits/tti-ai-chat/StructuredOutput.jsx ships three primitives for AI-generated structured tool output inline in a conversation: InlineCard, InlineCarousel, ResponseCard. Anatomy lineage is MCP Apps for Claude; translated to tti-ux's typography, color, and signature language. Globals: React, LucideIcon, TuxBadge — all already provided by other kit scripts.
  • public/kits/tti-ai-chat/index.html rewritten to register StructuredOutput.jsx and to demonstrate the new primitives — the assistant Message in the demo conversation now renders an InlineCarousel of six citation cards, a ResponseCard for the Linear ticket draft (with toolName, status, footer), and a compact InlineCard for the related runbook. Title kept as tti-ai-chat to match the local kit naming.

1.3.0 — 2026-05-12

Shell + sidebar + footer alignment batch. The style-guide chrome now dogfoods TuxDocsSidebar (and gets the same exclusive-open disclosure UX as the AggieUX reference kit), the institutional footer is reconciled to the comm-team's Kadence source so we ship the same handles + link inventory as production tti.tamu.edu, and the shell adopts the tapered-hairline pattern from tti-ai-studio for cross-product consistency. No breaking API changes — every component prop added below is optional with a sensible default.

Direction — tti-ux as source of truth for the BI design system

  • ADR 0009 accepted (docs/adr/0009-bi-design-system-source-of-truth.md). tti-ux becomes the canonical home for TTI's Power BI / Microsoft Fabric design system: brand tokens (already here), data-viz tokens (already here), Power BI theme JSON (to be generated here from tokens), and the Aggie Viz / Tti Viz shell spec (to be ported here from docs-it-tamu-edu).
  • Consumers: tti-reporting (ttitamu/tti-reporting) currently snapshots tokens into core/themes/tokens.json and ports the Aggie Viz shell into core/ttiviz/SHELL_SPEC.md. Once tti-ux owns these, those become reference snapshots that follow tti-ux releases.
  • docs-tti-tamu-edu stops being a stale clone of the docs-it-tamu-edu Power BI section and instead links to tti-ux for canonical BI standards.
  • Pending port (from docs-it-tamu-edu/nuxt-site/content/docs/tamu/M365/Power-BI-Fabric/): ~12 documents covering AggieBI brand-theme rationale, light/dark mode architecture, organizational visuals, component/visual-type policy, and the Aggie Viz visual spec family (shell, textboxes, nav pills, card styling, PBIP property reference). See ADR 0009 for the full mapping.

Changed — Style-guide shell dogfoods TuxDocsSidebar

  • Shell sidebar collapses by group. The flat list in app/app.vue is replaced with <TuxDocsSidebar> (the component was already shipping for downstream consumers and even documented the style-guide shell as a TODO migration). Auto-opens the group containing the active route, persists collapse state in sessionStorage under tti-ux-sidebar, and exposes the inline filter as "Filter the system…". Eliminates the wall-of-69 problem when scanning the catalog.
  • Exclusive-open root: a new exclusiveTopLevel prop on TuxDocsSidebar makes opening any root group explicitly close every other root group. Without this you could expand 5 groups and lose the sidebar's "where am I" affordance. Opt-in (default false) so existing consumers (PECAN docs shells, etc.) aren't surprised.
  • Long-name truncation: leaf-link labels now share the __label truncation class with summary rows. Component names like TuxAnnouncementBanner were blowing past the 240px lane and — combined with the CSS spec coercing overflow-x to auto whenever overflow-y is non-visible — surfaced a horizontal scrollbar inside __nav. The root cause was __sublist's display: grid auto-sizing the column to max-content; pinning to grid-template-columns: minmax(0, 1fr) lets the column shrink below max-content so the ellipsis actually kicks in. __nav also gets an explicit overflow-x: hidden as belt-and- braces against future inner-layout drift.
  • Sticky sidebar shell: the shell wrapper in app.vue is now md:sticky md:top-[57px] md:self-start md:max-h-[calc(100vh-57px)] on desktop with overflow-y: auto; overflow-x: hidden. Before, the wrapper stretched to match the main content's full height, so scrolling the page scrolled the sidebar out of view and the tapered hairline rode down into the footer. self-start opts out of the flex parent's default stretch so sticky can actually take effect; mobile keeps its fixed inset-y-0 top-[57px] slide-in shell unchanged.
  • Content width: shell content cap raised from max-w-5xl (1024px) to max-w-6xl (1152px). Modest bump — leaves room for the future right-rail TuxTOC and the persistent left sidebar.

Changed — TuxFooter aligned to the comm-team Kadence source

The comm team's authoritative footer (Kadence block source for production tti.tamu.edu) now defines the institutional shape. We were close on structure but drifted on handles, URLs, and the TAMUS lockup styling. Reconciled in one pass.

  • Social handles flipped to the canonical ttitamu (no S) forms: LinkedIn linkedin.com/company/texasa-mtransportationinstitute, Facebook facebook.com/ttitamu, Instagram instagram.com/ttitamu/, YouTube youtube.com/ttitamu, Twitter/X twitter.com/TTITAMU. Previously we shipped a stale TTIVideoChannel YouTube link and TTITAMUS (with S) for several others.
  • Threads added (sixth social, matching production). Lucide doesn't ship a Threads glyph, so TuxFooter now accepts an optional svg?: string field on SocialLink as an alternative to icon — pass raw <svg> markup. Threads renders via the Simple Icons path (CC0). Force-set fill="currentColor" on the <svg> and <path> plus a descendant CSS rule so the icon picks up the footer's white color reliably across v-html paths.
  • Social-link tap targets bumped from 40×40 to 44×44px — WCAG 2.5.5 (AAA) target-size minimum, matching the Kadence 44px setting. Icon glyph scaled from 1.125rem to 1.25rem for proportion.
  • State Resources URLs corrected to match Kadence: Texas Homeland Security points at gov.texas.gov/ root, Statewide Search at tsl.texas.gov/trail/index.html.
  • Policies column restructured to the 9-item TTI-owned list (TAMUS Risk/Fraud Hotline → Digital Accessibility → Site Policies → Open Records → Statutorily Required Reports → TTI Rules → Veterans → Equal Opportunity → Jobs) — all pointing at tti.tamu.edu/notices-policies/… rather than the upstream tamus.edu URLs we previously linked. The internal WCAG-audit page is kept as the column's last entry (style-guide-only extra, not in production).
  • Tagline dropped from the marketing section. Kadence's production footer doesn't render the "Coordinated Statewide Transportation Research Program" tagline — institutional name carries it. The tagline prop still defaults to that string for back-compat; the shell passes tagline="" to suppress it.
  • TAMUS lockup → plain link. The black legal strip's left side previously rendered as a two-line eyebrow + name lockup. Kadence ships a single plain "A member of the Texas A&M University System" link. Lockup CSS removed; replaced with a single __tamus-link rule.
  • © line now linked. Two new optional props on TuxFooter: copyrightText (full override of the default "© {year} {name}" rendering) and copyrightHref (wraps the line in an external link). The shell passes © Copyright 2026 Texas A&M Transportation Institute (TTI) pointing at the copyright-statement page, matching production.

Added — Tapered hairlines (cross-product shell pattern)

  • .tti-shell-header::after / .tti-shell-sidebar::after — the hard border-b / border-r Tailwind utilities on the shell header and sidebar are replaced with 1px pseudo-elements drawn via linear-gradient(transparent 0% → var(--surface-border) 18%–82% → transparent 100%). Reads as a soft ruled line that fades into the surface rather than a hard corner-to-corner stroke. Lifted verbatim from tti-ai-studio's studio-shell (same 18%/82% stops) so the two products feel like one family at the chrome level. Promote to a shared utility when a third surface needs it.

Removed — Home page Components grid

  • The 69-card Components grid on the welcome page is gone. The same inventory is now reachable in one click via the (newly collapsible) sidebar's Components group and the dedicated /components/ catalog page, both of which already shipped. The grid was duplicating navigation and dominating the home page scroll. catalogCount constant added for the hero meta line ("70+ components · 5 foundations") so we don't have to maintain a parallel array.

1.2.0 — 2026-05-08

Cuts the accumulated post-v1.1.0 work into a tagged release. Two component batches (data density + geographic charts) recovered from a worktree wipe, the visual-language evolution prelude that should have shipped before them, and an npm audit cleanup. No breaking changes — pin bump from #v1.1.0 to #v1.2.0 is additive only.

Added — Visual-language evolution prelude (2026-05)

Token-only refresh that should have shipped before the data-density batch below. No per-component edits required for the base behavior; families inherit the changes automatically.

  • Two-ring focus token — outer 2px maroon + inner 2px sand halo replaces the prior single 3px maroon-at-35% ring. Reads cleanly on data tables and form rows where a single soft ring gets lost. New tokens: --focus-ring-outer, --focus-ring-inner. Variants: dark uses softened maroon + warm-gold inner; HC uses pure black + white inner at 5px total width. Lineage: Ant (two-ring concept) and Fabric (focus discipline on data-rich surfaces); color story is tux-original.
  • Transportation-tempo easings--ease-survey (tables/forms/ disclosures), --ease-corridor (page-level/sheets/modals), and --ease-arrival (toasts/banners/snapshots locking in). Three distinct curves giving "measured / smooth / decelerate-only" coverage without overshoot. Legacy --ease-standard/emphasis/exit retained as back-compat aliases. Lineage: Material 3 standard-effects family.
  • Four-tier elevation system--elevation-flat/rest/hover/ overlay/pinned, layered on existing --shadow-sm/md/lg. Gives every component a predictable elevation role; old sm/md/lg are still available as primitives but new work should use the named tiers. Variants: dark bumps overlay alpha so sheets stay readable on warm charcoal; HC nukes shadows across the board and uses a 2px border for the overlay tier. Lineage: Microsoft Fabric.
  • Warm-neutral ramp extension — added intermediate stops at --neutral-150/250/450/550/650/750/850. Sand-leaning tints that harmonize with maroon over extended viewing. Use for row-stripe, hover, and selected states on data-table surfaces (the original 6-stop ramp couldn't carry those without compounding transparent tints). Lineage: shadcn's stone/zinc 12-stop ramps; hex values tux-original.
  • Survey-rhythm density tokens--rhythm-tight/snug/normal/ loose/roomy (4/8/12/16/24px). Use instead of the general --space-* ramp inside dense surfaces (tables, forms, descriptions); --space-* stays for section/page-level rhythm. Lineage: Ant Design's 4/8/12/16/24 cadence.
  • public/identity-primitives.svg — four <symbol>s exported as a single shared sprite: #tux-star (5-point Lone-star reference), #tux-chevron (TAMUS-style downward angle bracket), #tux-compass (compass rose for map-related families), and #tux-row-grid (parallel-lines-getting-denser pavement-stripe pattern). Restraint-grade identity moves for corner accents, section brackets, and map decorators. All draw with currentColor.
  • design/visual-language-evolution.md — running ledger that documents each entry above (what changed / why / variants / lineage). Future token-level changes get appended here.

Added — Data density + geographic charts (2026-05)

Two batches recovered after a worktree got wiped by a parallel session before any of its work was committed. Rebuilt verbatim from chat memory + the official source of truth (us-atlas + TxDOT MapServer).

Data density:

  • TuxRichDataGrid — interactive grid for PECAN-class operational surfaces. Sticky header, row selection w/ indeterminate header checkbox, expandable detail rows, sortable columns, active-filter chip strip, bulk-action bar (visible only when ≥1 row selected), footer pagination strip. State is host-driven via v-models (selected / expanded / sortKey / sortDir) and events (search, filter-remove, bulk-action, page, toolbar). Slots: cell-<key> per column, expanded for row body, bulk-actions to override the default action trio.
  • TuxDataTable — research-flavored static table for finished deliverables. Numbered caption (Table 4-2) over an Oswald display title + optional descriptive lede; tabular- figure body cells; uncertainty cells auto-rendered as value ± CI when a column declares ciKey; footnote anchors wired to a formal note block; optional row groups banded by category; optional sticky header w/ max-height for appendix density; optional totals row w/ maroon top-rule; source citation line. Sort is host-driven.

Geographic charts (real geometry):

  • TuxChartGeographic — five Texas-flavored map kinds in one component:
    • county — 254 real TIGER/Line counties projected with d3-geoAlbers (Texas-centered, parallels 27°N–35°N).
    • districts — TxDOT's 25 engineering districts using the official boundary geometry from the TxDOT MapServer feature service, Visvalingam-simplified at weight 0.05.
    • us-context — All 50 US states + DC using the AlbersUsa composite projection (handles AK / HI as insets natively). Highlighted state shifts to maroon.
    • dot-density — Dots are rejection-sampled inside the actual Texas state outline using a deterministic Mulberry32 RNG so SSR + CSR produce the same scatter.
    • flow — Origin-destination curved arcs between Texas's seven primary metros, projected from real lat/lng.
  • TuxMetroInset — single-metro neighborhood-grid companion for 4-up drill-downs (Houston · DFW · Austin · SAT). Cell pattern seeded by metro name so SSR + CSR match.
  • TuxChartSunburst — two-ring radial breakdown sister to TuxTreemap. Inner ring = top-level groups, outer ring = children with stepped opacity, center carries total in tabular numerals. Container-queried legend collapse below 36rem.
  • TuxChartFrame — editorial wrapper (eyebrow + Oswald title + 2px maroon signature rule + body slot + source/notes footer) used by /visualizations/* showcase pages so a multi-exhibit report reads as one document.

Geometry pipeline (build-time only):

  • scripts/build-geo.mjs runs at build time only — pulls us-atlas (npm; TIGER/Line 1:10m) and the official TxDOT MapServer feature service, simplifies via topojson-simplify, projects with d3-geoAlbers (Texas) / d3-geoAlbersUsa (US), emits three TS modules under app/assets/geo/ (texas.ts, texas-counties.ts, us-states.ts). Outputs are checked in; re-run via npm run build:geo when upstream sources change. No runtime projection or topology library — the component imports static SVG path strings.
  • New devDependencies: d3-geo, topojson-client, topojson-server, topojson-simplify, us-atlas.
  • New tokens: --chart-1 through --chart-8 (categorical palette across light / dark / HC), --map-seq-maroon-1..5, --map-seq-slate-1..5, --map-outline, --map-flow. All three theme variants override.
  • data/source/ added to .gitignore (raw GeoJSON is 13MB+ and re-fetchable from the TxDOT MapServer).

Security — npm audit cleanup, 8 → 0 vulnerabilities (2026-05)

  • npm audit fix (no --force) applied patch upgrades to nitropack (open-redirect via wildcard route rules + proxy scope bypass), uuid (missing buffer-bounds check in v3/v5/v6), and basic-ftp (unbounded multiline FTP control buffer DoS, transitive via Puppeteer's proxy-agent chain).
  • potrace removed from devDependencies. The remaining five vulns were a single root cause counted across the phin@2.x → @jimp/core → @jimp/custom → jimp → potrace chain. Potrace was a build-time-only utility (the SVGs it produced ship in public/); the script scripts/png-to-svg-logo.mjs now starts with a fail-fast that prints regen instructions (npm install --no-save potrace jimp@0.16).

npm audit reports found 0 vulnerabilities on a clean install.

Docs — doctrine catch-up

  • design/components.md — added rows for TuxDataTable, TuxRichDataGrid (main table) and TuxChartFrame, TuxChartGeographic, TuxChartSunburst, TuxMetroInset (Visualizations table). "Want X? use Y" pattern map gains five entries (rich data grid, static research table, Texas map, multi-metro inset, two-ring radial). Component count bumped ~60 → ~70.
  • design/roadmap.md — new "Recently shipped" section covering the data-density and geographic-charts batches plus the visual-language evolution prelude. Notes that data-density wasn't on the original roadmap and the geographic-charts work is adjacent to (not a replacement for) the still-aspirational TuxMapEmbed Mapbox/Leaflet line.

1.1.0 — 2026-05-06

Cuts the accumulated post-v1.0.0 work into a tagged release so consumers can pin to github:ttitamu/tti-ux#v1.1.0 and upgrade deliberately. Substantial new features in the hybrid-chrome family (below) plus packaging changes that make the GitHub-tag consumption path real.

Added — packaging for GitHub-tag consumption

  • public/CNAME for ux.tti.tamu.edu. Pins the custom domain in the Pages artifact so deploys don't drop the Settings → Pages custom-domain value on republish — the source of recent HTTPS cert flakiness, since the cert is provisioned per-domain and only after the domain is locked in.
  • main + files in package.json so consumers get a clean package shape: layer config (nuxt.config.ts), app/, design/, docs/, public/, plus README/CHANGELOG/tsconfig. Everything else (scripts, contrast-report, dist, node_modules) stays out.
  • Runtime deps moved from devDependencies to dependencies@iconify-json/lucide, @tailwindcss/vite, @tanstack/vue-table, @tanstack/vue-virtual, tailwindcss. These are needed at build time in any consumer that extends the layer or installs the package, so they have to be runtime deps. (The pure tooling deps — @nuxt/devtools, puppeteer, potrace, serve-handler, typescript — stay in devDependencies.)
  • README consumption guide rewritten around extends: ["github:ttitamu/tti-ux#v1.1.0"] as the primary path, with file:../tti-ux kept as a local-dev fallback.

Hybrid chrome (PECAN-driven)

App-shape consumers (PECAN, tti-ai-studio) need a "I'm in an application" chrome that wasn't quite expressible with the current TuxSiteNav + TuxDropdown + TuxMegaMenu set. v1.1.0 adds the missing hooks so the institutional + docs + app patterns can all share one component family. All changes additive; no breaking changes for marketing-surface consumers.

Added — hybrid chrome

  • TuxSiteNav #trailing slot — renders inside .tux-site-nav__bar-inner after the primary nav, with a left-border separator from the nav. App-shape consumers drop a utility cluster (search trigger, notifications bell, theme toggle, user dropdown) into the slot; marketing surfaces leave it empty and rely on the upper utility bar (utilityNav prop).
  • PrimaryNavItem.to — now valid alongside dropdown / megaMenu. When set, the trigger LABEL becomes a real NuxtLink (clicking → navigate; hover/focus still opens the panel). Use the route the operator most likely wants when they "just click on the section name" — typically the area's overview/index.
  • Route-aware active states on TuxDropdown + TuxMegaMenu. Each menu computes:
    • isTriggerActive — true when the route matches to exactly OR is a strict descendant path OR matches any descendant item's to. Renders the trigger with a 2px maroon bottom-border (with reserved-slot pattern so the active state doesn't shift the label vertically).
    • isItemActive(item) — exact-route match. Renders the item with --active modifier: 9% maroon background tint + 2px maroon left rail (via box-shadow: inset) + bold label + aria-current="page".
    • Auto-close on navigation via watch(() => route.fullPath, …). Same --active shape applied to plain primary-nav links via TuxSiteNav.isPlainLinkActive so all three primary-nav variants share one visual language.

Fixed

  • Active-state on ?query= and #hash routesTuxDropdown.isItemActive, TuxMegaMenu.isItemActive, and TuxSiteNav.isPlainLinkActive were comparing route.path === target which strips both the query and hash. Items like /policies?tab=approvals and /admin#fleet never lit up; worse, on a query-filtered route the bare-path sibling (/policies) STAYED active, so two siblings claimed the indicator at once. Extracted the matching rules into a shared app/utils/nav-active.ts helper with two functions:
    • isExactActive(target, route) — segment-aware exact match (path + query + hash). A bare-path target only lights up on a "neutral" route (no query, no hash); a query/hash target requires an exact match. This is what individual items + aria-current="page" use.
    • isSectionActive(target, route) — path-only prefix match. This is what triggers and section indicators use, since ?tab= / #anchor don't change which section the operator is in. Also picked up by TuxDocsSidebar so doc anchors work the same way.
  • Trigger + item clickability under hover-open stateTuxDropdown / TuxMegaMenu / TuxSiteNav / TuxFooter were rendering navigable elements via <component :is="triggerComponent">
    • v-bind="linkAttrs(item)" where triggerComponent resolved a NuxtLink from a string at runtime. Under hover-open state, clicks on the trigger were being eaten intermittently (vue-router didn't always have the resolved component reference at click-handling time). Replaced with explicit <NuxtLink v-if> / <a v-else-if> / <button v-else> branches across all four components — vue-router gets the static component reference at compile time and clicks land reliably. Verbose, but the cost is cheap and the bug it prevents is user-visible.

Changed

  • TuxMegaMenu panel sizing — was position: absolute; left: 0; right: 0; max-width: 80rem but anchored to the trigger <li> (which had position: relative for TuxDropdown's per-trigger panels). Result: panel inherited the narrow trigger box; columns stacked vertically because auto-fit minmax(11rem, 1fr) had no horizontal room. Fixed by anchoring the panel right-edge-to-trigger (right: 0; left: auto), sizing it to its content (width: max-content), capping at min(48rem, calc(100vw - 2rem)) with a min-width: 20rem. Three columns now lay out side-by-side; visually consistent with TuxDropdown (same border, shadow, density).
  • Layer-rooted CSS paths in nuxt.config.ts~/ resolves to the consumer's app/, not the layer's, so ~/assets/css/tokens.css in the layer's nuxt.config breaks consumers when they extends the layer. Now uses dirname(fileURLToPath(globalThis._importMeta_.url))resolve(layerDir, "app/assets/css/..."). Same fix applied to app/pages/icons.vue's import (was ~/utils/lucide-names, now ../utils/lucide-names).

Token additions

  • TTI gold palette added to app/assets/css/tokens.css (50→950 anchored at #DDAC37 = 500). Was a planned follow-up documented in v1.0.0; now upstream so any consumer can flip warning: amber → gold in their app.config.ts without a local tokens.css shim.

Documentation

  • Internal-tool footer exception. The "one footer component, vary content via columns and social props" guidance still applies to public institutional surfaces. But internal-tool consumers (PECAN, future internal admin tools) should drop TuxFooter entirely — the marcom shape (state-resource columns, social row, TAMUS legal strip) is sized for tti.tamu.edu, not for single-audience tools whose operators already know they work for the institute. PECAN ships its own PecanOpsFooter (~40px slim strip with version pill + helpdesk + © anchor) as the documented exception.

1.0.0 — 2026-04-27

First versioned release. The catalog has been stable for three product consumers (PECAN, tti-ai-studio, the style guide itself) for several weeks; cutting v1 makes the consumption story (file:../tti-ux for now, npm package later) explicit instead of implicit.

Added — accessibility tooling + AAA conformance

  • /accessibility page with the formal conformance statement: WCAG 2.2 AA target; color contrast verified at AAA across all three themes (light, dark, high-contrast).
  • /contrast-audit page renders every contrast-risk surface in three themed columns simultaneously (one per data-theme).
  • scripts/audit-contrast.mjs uses puppeteer + the WCAG 2.1/2.2 contrast formula to compute ratios for every text/background pair on the audit page. Reports both AA and AAA pass counts in one run; gates on AAA when AUDIT_LEVEL=AAA is set (CI default).
  • audit-contrast.yml workflow runs on every push + PR. CI fails if any contrast pair drops below AAA.
  • Token revisions to clear AAA across the board:
    • light --text-secondary #5d5d5d → #424242 (8.97:1 on white)
    • light --text-muted #6F6F6F → #525252 (7.05:1 on white)
    • light --color-success → #3D5328, --color-error → #A02828, --color-info → #1F5D66
    • dark --brand-primary #d2718c → #e795a8 (7.92:1 on dark page)
    • dark --brand-secondary → #8ab7e2, --color-error → #F59292, --color-success → #ABCC8E, --color-info → #9BD4E0
    • HC --text-muted → #4D4D4D, --color-error → #A02828
    • new --brand-fill token for filled maroon panels (always #5C0025 in light/dark, #500000 in HC) so brand-primary can lighten in dark mode for text legibility without compromising panel contrast.
  • Tailwind v4 @theme cascade workaround: re-bound every --color-* alias inside [data-theme="tti-dark"] and [data-theme="tti-hc"] because Chrome freezes @theme variable references at :root scope. Without this, theme overrides on raw tokens didn't propagate to Tailwind utility classes.

Changed — BREAKING

  • TuxFooter is now the unified institutional footer. Earlier releases shipped three separate components for the page anchor (slim TuxFooter, TuxMarketingFooter, TuxSubfooter); they're collapsed into a single TuxFooter that renders the maroon marketing block + the mandatory TAMUS legal strip in one component. Every shipped TTI surface needs the same anchor — having three pieces was over-decomposition.
    • Migration: replace <TuxFooter ... /><TuxSubfooter /> (and any <TuxMarketingFooter /> usage) with a single <TuxFooter :columns="..." :social="..." />. Pass empty arrays for columns and social to get the slim app-shape (just identity block + legal strip).
    • Removed components: TuxMarketingFooter (lived for one commit), TuxSubfooter (replaced by the legal-strip section of the new TuxFooter).

Added

  • /changelog page — renders this file via the same MDC pipeline as /design/[doc]. Linked from the welcome page header and the "What's new" section.
  • "Welcome" sidebar group — Home + Changelog above Foundations.
  • v1.0.0 version pill — surfaced in the header lockup and the welcome eyebrow, sourced from package.json.
  • Welcome-page polish — "Recent updates" timeline, CTA buttons (Doctrine / Components / Repo), tighter pitch covering all three downstream consumers.

Unreleased

Added — Priority A roadmap batch (2026-04-29)

Status states, banners & tags, and dedicated form-primitive state-matrix pages. Closes Priority A in design/roadmap.md; ~60 Tux* components total now in the catalog.

Status states (3 new components):

  • TuxErrorPage (app/components/TuxErrorPage.vue) — full-page 404 / 500 / 403 / 503 template. Editorial display-type status numeral, icon medallion, title + lede, recovery actions (TuxButton row), optional support / status-page slot. Per-code defaults (icons, copy, actions); slots and props override anything. Use as Nuxt's error.vue or inline via the inline prop.
  • TuxSkeleton (app/components/TuxSkeleton.vue) — placeholder shapes for loading states. Six composed presets (card / list / table / article / media / stat) plus primitive mode (width / height / variant). Three animation modes: shimmer (default · 1.6s linear gradient sweep), pulse (cheaper opacity oscillation for dense lists), never (opt-out for print / screenshots). Honors prefers-reduced-motion: reduce automatically — collapses to a static tint, no movement, per the roadmap spec. SR-only "Loading…" label on a role="status" region; decorative shapes are aria-hidden.
  • TuxStepper (app/components/TuxStepper.vue) — numbered-circle multi-step indicator. Status auto-derives from currentIndex (done · active · todo); per-step error override. Two orientations (horizontal collapses to vertical below ~30rem container width via container query, not viewport). Optional to per step renders the node as NuxtLink for jump-back navigation. Renders as <nav aria-label> with aria-current="step" on the active item. Use for funding applications, IRB submissions, study onboarding.

Banners & tags (3 new components):

  • TuxAnnouncementBanner (app/components/TuxAnnouncementBanner.vue) — top-of-page dismissable strip for site-wide notices. Distinct from TuxAlert (page-body admonition) — this is chrome that sits above the page header, full-width, persists across routes. Dismissal persists in localStorage keyed by id prop; emits dismiss for consumers driving their own persistence. Four tones: info (blue), success (green), warning (gold), urgent (maroon fill — reserves for service outages, security advisories).
  • TuxCookieConsent (app/components/TuxCookieConsent.vue) — privacy notice required on public TTI surfaces. Bottom-right floating card or full-width bottom strip (position prop). Decision (accepted / rejected / custom) persists in localStorage and emits via decision event so consumers can gate analytics. Surface only; per-category UI lives in the #categories slot — the host owns category names and wiring. Necessary cookies are implicitly accepted (no toggle).
  • TuxBetaRibbon (app/components/TuxBetaRibbon.vue) — environment / lifecycle label. Three variants picked by host context, not aesthetic preference: corner (diagonal ribbon in the page corner — full-deploy preview environments / staging), stripe (top-of-page horizontal full-width — production-shaped deploys with non-prod data, public betas), pill (inline — feature- level beta tags, per-page indicators). Three tones: preview (gold), beta (navy), dev (warning red). Maintenance windows belong on TuxAnnouncementBanner tone="urgent" — content notice, not chrome label.

Form primitives — 6 dedicated state-matrix pages, no new components: the existing /forms page explicitly stated "No Tux wrappers here yet" — Nuxt UI's form primitives don't need deviation to fit the brand. The roadmap's intent was breaking the inline catch-all into focused per-primitive pages, not new wrappers.

  • /forms is now a landing (app/pages/forms/index.vue) with 6 tiles + a link to the legacy all-in-one demo (now at /forms/all-in-one).
  • /forms/text-fieldUInput + UTextarea state matrix: default · focus · filled · disabled · readonly · error · with helper · with counter · auto-resize · trailing button · leading icon.
  • /forms/selectUSelectMenu with descriptions (TAMUS classification tiers), searchable combobox, creatable values, multi-select.
  • /forms/choiceUCheckbox group with select-all + indeterminate parent; URadioGroup with descriptions and inline orientation; "when to reach for which" decision section.
  • /forms/date-picker — native UInput type="date" with helper, min/max bounds, range-input pattern, plus time / datetime-local / month / week.
  • /forms/file-upload — drag-drop dropzone, file list with per-file progress + status icons, error and complete states. Working sketch; consuming apps wire the actual upload backend.
  • /forms/inline-validation — live patterns: character counter, phone-number mask, NIH grant ID mask, async "this email is taken" with debounced check. Inline patterns; "why no Tux wrapper" rationale.

Wiring:

  • Sidebar nav: 6 new entries in the alpha-sorted Components group (TuxAnnouncementBanner, TuxBetaRibbon, TuxCookieConsent, TuxErrorPage, TuxSkeleton, TuxStepper); the "Forms" entry in the Composition group is replaced by a dedicated Forms group with an overview link and 6 sub-pages + the all-in-one demo. Components catalog (components/index.vue)
    • home grid (pages/index.vue) + doctrine table + pattern coverage map all updated.
  • New pattern-coverage entries: "Loading placeholder" → Skeleton, "404 / 500 page" → ErrorPage, "Multi-step flow indicator" → Stepper, "Site-wide notice strip" → AnnouncementBanner, "This isn't production label" → BetaRibbon, "Privacy / cookie consent" → CookieConsent.

Added — design-kit port (chat primitives + reports section)

  • Chat primitives — five components ported from the tti-ai-chat React/JSX kit. They were previously demo mocks; now they're real Vue SFCs in the catalog so a future tti-ai-chat Nuxt project can consume them via the same file:../tti-ux link the other consumers use.
    • TuxChatMessage (app/components/TuxChatMessage.vue) — one conversation turn. role="user" or role="assistant"; the assistant variant sits on --surface-sunken so model output reads as a distinct visual lane. Slots for citations, tools, and avatar.
    • TuxCitations (app/components/TuxCitations.vue) — numbered source list under an assistant message. Three-column grid (rank · body · score) so paths can ellipsize without pushing the score off-screen.
    • TuxComposer (app/components/TuxComposer.vue) — chat input with optional compliance scope banner. Two-zone layout (textarea + toolbar). Built-in model picker, corpus-attach, char counter, and ⌘↵-to-send. Slot in a TuxAlert variant="compliance" via the #scope slot for ITAR / restricted-corpus framing.
    • TuxConversationList (app/components/TuxConversationList.vue) — left-rail history grouped by temporal bucket (TODAY · YESTERDAY · THIS WEEK · …). 3px maroon left border + 10%-tint background on the active item.
    • TuxContextPanel (app/components/TuxContextPanel.vue) — right-rail surface for grounding context (corpus, retrieval, usage). Slot-driven; the panel contributes the chrome (border, scroll, width, padding scheme) and the host arranges sections.
  • New /reports section — finished-narrative deliverables. Paper · PDF · print. The reader reads top-to-bottom.
    • TuxReportFrame (app/components/TuxReportFrame.vue) — page-sized canvas (letter / a4, portrait or landscape) for PDF export and print. Editorial or compact density. Draws a paper-sheet shadow on screen; goes flush borderless under @media print. Visualizations, stats, and prose go inside.
    • TuxReportPrintSheet (app/components/TuxReportPrintSheet.vue) — drop-in print stylesheet. Renders nothing on screen; injects a <style media="print"> into the head via useHead. Hides chrome marked data-print="hide"; paginates with data-print-break="before|after|avoid".
    • Reports landing (app/pages/reports/index.vue) — overview tiles, scope explainer (what's a report vs a viz).
  • New /visualizations section — interactive data surfaces. BI dashboards · R artifacts · future native charts. The reader pivots, filters, drills in. Distinct from Reports because the chrome and posture are different (provider chip, sandbox, source caption), and the deliverable lives inside the app, not on paper.
    • TuxVizEmbed (app/components/TuxVizEmbed.vue) — branded chrome around a Tableau / Power BI / Apache Superset / Grafana iframe. Provider chip + open-in-new + per-provider sandbox + loading/error states. New posterSrc prop renders a static image fallback when the live tenant isn't reachable (style-guide demos, air-gapped previews).
    • TuxVizRPlot (app/components/TuxVizRPlot.vue) — branded chrome around an R-language plot artifact. Three kinds (image PNG/JPG, svg via <object>, html for htmlwidgets in a sandboxed iframe). Source-line caption for sessionInfo() or script path.
    • Visualizations landing (app/pages/visualizations/index.vue) — overview tiles, reports-vs-viz explainer, link to roadmap for native charts.
  • Demo poster artwork so visualization demos render even without a live tenant: public/viz-poster-tableau.svg (TTI-corridor crash-rate bars), public/viz-poster-powerbi.svg (sponsored-research executive overview), public/viz-poster-grafana.svg (PECAN ingestion time-series + heatmap), public/viz-rplot-grants.svg (faceted ggplot2 by quarter).
  • TTI road-glyph SVG (public/TTI-glyph.svg) — institutional road mark isolated from the full TTI lockup. Used as the TuxChatMessage assistant avatar (replaces the earlier tx italic placeholder).
  • Sidebar nav: new "Reports" + "Visualizations" groups between "Composition" and "Tooling". Reports group has Frame + PrintSheet; Visualizations group has Embed + RPlot.
  • Sidebar housekeeping: added "Roadmap" link in the Design group (/design/roadmap was reachable but unlinked); collapsed the redundant TuxSiteNav / TuxDropdown / TuxMegaMenu trio (all three pointed at the same demo page) into a single TuxSiteNav entry.

Added — reports + visualizations build-out (2026-04-29)

Rounds out both new sections so they read as more than the two-component minimum the design-kit port shipped with.

  • TuxReportWebFrame (app/components/TuxReportWebFrame.vue) — long-form, web-hosted narrative canvas. Same Reports family as TuxReportFrame (finished narrative, read top-to-bottom), but rendered as an HTML page rather than an 8.5×11 sheet. Cover (eyebrow / title / lede), byline + date + reading-time row, optional sticky right-rail TOC, body slot with editorial typography rhythm, footer slot for source / publication line. Three measure widths (narrow ≈ 56ch, default ≈ 70ch, wide ≈ 82ch). Sibling sidebar entry under Reports.
    • **Why a new component instead of just composing TuxPageHeader
      • free sections:** TTI's web-hosted reports (annual report, research findings, accreditation summaries) want a deliberately bounded reading frame with cover, byline, optional TOC, and closing source line. Composing those primitives ad-hoc per page drifted; the frame canonicalizes the rhythm.
    • Why it's a Report, not a Visualization: the reader still reads top-to-bottom. The web/paper distinction is output medium, not posture. A Visualization is something the reader pivots and filters.
    • Demo at /reports/web-frame with a full sponsored-research example (cover · TOC · summary · findings with a TuxFactoid · methods · data · what's next).
  • TuxSparkline (app/components/TuxSparkline.vue) — inline mini trend line. No axes, no legend, no tooltip. Native SVG; no chart library. Five tones (brand / success / error / warning / neutral), all theme-aware across light / dark / HC. Optional area fill, last-point marker, and right-aligned delta arrow (+12% percent or +4.9 absolute). The SVG carries an auto-derived aria-label and matching <title> summarizing the trend ("Trend: 10 points, low 61, high 84, last 84 (+35.5% from first)"); pass units="$M awarded" to suffix the summary.
    • First native chart in the catalog. Shipped ahead of the full TuxChartBar / TuxChartLine family because it's small (no axes, no legend, no library) and the demand was already clear: pair with TuxBigStat / TuxFactoid for KPI rows.
    • Demo at /visualizations/sparkline.
  • TuxVizGrid (app/components/TuxVizGrid.vue) — small-multiples layout primitive for the Visualizations section. 2-, 3-, or 4-up panes of TuxVizEmbed / TuxVizRPlot (or any visualization-shaped child) with shared editorial header (eyebrow / title / dek), consistent gap, and optional footer slot for source / data-cutoff / caveat copy. Container-queried — collapses to single-column under ~32rem and to 2-up between ~32rem and ~56rem. Doesn't repaint pane chrome; layout only.
    • Why a layout primitive instead of just CSS Grid in the host: a shared editorial header above N panes is a recurring pattern (an exec dashboard with three Power BI tiles; a regional small-multiples set of one Tableau viz). The grid implies "these belong together" — meaningful editorial signal, worth a component.
    • Demo at /visualizations/grid with two-up Tableau + Power BI and three-up Grafana stages.
  • Reports landing rewritten (app/pages/reports/index.vue) — three tiles instead of two, each tagged with the output medium (paper · PDF / print stylesheet / web-hosted). New "Pick by output medium" decision section.
  • Visualizations landing rewritten (app/pages/visualizations/index.vue) — four tiles, each tagged with kind (BI iframe wrapper / R artifact wrapper / layout primitive / native mini chart). Scope note now reads "TuxSparkline ships as the first native chart; the broader family is still on the roadmap" instead of "all native charts are pending".
  • Doctrine table updates (design/components.md) — Reports table grows an "Output medium" column (paper · PDF / print stylesheet / web (HTML page)). Visualizations table picks up TuxVizGrid + TuxSparkline rows. Pattern coverage map picks up six new "want X? use Y" entries (sparkline, the three report flavors, BI dashboard, R artifact, small-multiples).
  • Roadmap reconciled (design/roadmap.md) — "Recently shipped" block updated to current names (TuxReportEmbed / TuxReportRPlot were renamed to TuxVizEmbed / TuxVizRPlot mid-batch and the roadmap still carried the old names). TuxSparkline crossed off Priority B.

Renamed (since 1.0.0; not yet released)

  • TuxReportEmbedTuxVizEmbed, demo route /reports/embed/visualizations/embed
  • TuxReportRPlotTuxVizRPlot, demo route /reports/rplot/visualizations/rplot
  • Rationale: Tableau / Power BI / Grafana / R artifacts are visualizations, not reports. TTI uses both words and they mean different things (reports = finished narrative on paper; visualizations = interactive surface in the app). The split makes the IA match the institutional vocabulary.
  • design/roadmap.md — Vue-shaped backlog adapted from the aggieux JSX-kit roadmap. Charts, maps, tabs, side-sheet, comments, tooltips, and a11y-doc pages organized by priority tier. Replaces the previous "we know what's missing but it's not written down" gap.
  • ADR-0008 — Data-display components stay flat; reports get their own section. (docs/adr/0008-data-display-and-reports-section.md) Documents the decision and the criteria for adding a future route group (chrome, discoverability, mass).
  • Motion + spacing tokens in tokens.css:
    • --motion-fast: 0.15s · --motion-base: 0.25s · --motion-slow: 0.35s
    • --ease-standard (default), --ease-emphasis (overshoot for the corner-drop signature), --ease-exit (symmetric ease-in-out).
    • --space-0, --space-4, --space-10, --radius-none — supplements for places Tux components reach into a CSS var directly (inline styles, calc()).

Added

  • TuxFooter brandLockup prop — official institutional artwork beneath the social row. The footer's identity column has the square glyph + HTML wordmark for chrome-density purposes; the new prop layers in the official horizontal lockup PNG (logo + wordmark in one piece) so consumers see the canonical institutional artwork on the maroon marketing ground. Defaults to /TTI_white.png for TTI; sibling institutions override with their own white-on- transparent variant; pass null to hide. Capped at 16rem × 2.75rem so it doesn't compete with the institution name above it.
    • Three new public assets: TTI-Color.png (maroon road glyph, light backgrounds), TTI-black.png (mono black, print/legal), TTI_white.png (light variant, dark/maroon backgrounds).
  • Doc-site chrome batch — TuxDocsSidebar, TuxTOC, TuxSiteNav, TuxDropdown, TuxMegaMenu. Fills the navigation-and-doc-shape gap identified after the previous batch. The catalog had TuxIdentity (the lockup) but no actual menu components, and no doc-site sidebar / TOC for technical documentation.
    • TuxDocsSidebar (app/components/TuxDocsSidebar.vue) — hierarchical sidebar built on native <details> + recursive TuxDocsSidebarNode. Active-route highlighting (including ancestors), inline search filter with match highlighting, sessionStorage-persisted collapse state per consumer.
    • TuxTOC (app/components/TuxTOC.vue) — article table-of-contents. Auto-detects H2/H3 from a target element on mount, tracks active heading via IntersectionObserver, smooth-scrolls on click, updates URL hash without triggering a full navigation. Pairs with TuxDocsSidebar for the canonical three-column doc layout.
    • TuxSiteNav (app/components/TuxSiteNav.vue) — institutional top-bar with TuxIdentity lockup + optional utility strip + primary nav. Mobile hamburger drawer included. Five AggieUX site types (University / Center / Department / Application+nav / Application-only) all expressible via composition.
    • TuxDropdown (app/components/TuxDropdown.vue) — single-column dropdown panel. Used as a primary-nav item in TuxSiteNav, also works standalone for inline "more actions" patterns. Hover/focus open with diagonal-path delay; Escape closes.
    • TuxMegaMenu (app/components/TuxMegaMenu.vue) — full-width multi-column panel from a top-bar item, with optional featured tile (eyebrow + title + description + image) on the right.

Changed

  • TuxFooter legal strip drops the gold "A" medallion next to the TAMUS lockup. The medallion was a placeholder glyph standing in for an actual TAMUS-system mark and read as ornament rather than signal; the eyebrow + name lockup carries the affordance on its own.
  • TuxDiagram styling pass. Swapped from Mermaid's default theme to base so all themeVariables actually apply; expanded the brand-mapping (primary/secondary/tertiary fills, sequence-actor styling, note styling for both light + dark modes); type sized down from 16px to 13px Open Sans so diagrams don't dominate editorial body context. Error surface replaced the raw parser dump with a hint about quoting unquoted special characters + a collapsible details for the parser output. Demo source for the flowchart fixed (the <br/> inside an unquoted node label was failing to parse — wrapped in "…" per Mermaid's syntax).

Added

  • Authoring + content batch — TuxCodeBlock, TuxDiagram, MDC integration. Closes the gap between "components for app surfaces" and "components for prose surfaces" (docs, blog, ADRs, marcom WordPress migration).
    • TuxCodeBlock (app/components/TuxCodeBlock.vue) — standalone Shiki-backed code block. Distinct from TuxExample (which is for component demos with Vue/HTML reveal tabs) — this one is for embedded code samples in docs/blog/ADRs. Lazy-loads the requested grammar on mount so the SSR bundle stays clean. Theme tracks page color-mode (light / dark / high-contrast). Filename caption
      • line numbers + copy button optional.
    • TuxDiagram (app/components/TuxDiagram.vue) — Mermaid wrapper for diagrams-as-code (flowcharts, sequence diagrams, ERDs, gantt). Mermaid is a real ~3MB dep, so the component lazy-imports it on mount — pages that don't render a diagram pay nothing. Brand palette mapped into Mermaid's themeVariables. Render errors surface inline with the parser message. Pulse + spin animations respect prefers-reduced-motion.
    • @nuxtjs/mdc integration — markdown rendering with Vue components inline. Configured in nuxt.config.ts with the same Shiki theme set the rest of the system uses. Tux components are auto-imported via Nuxt's existing resolver, so authors can invoke them with the ::tux-alert{variant="warning"} block syntax without per-component setup. Live demo + syntax crib sheet at /markdown shows source + rendered side-by-side.
  • Pattern coverage map in design/components.md — explicit "want X? use Y" table covering tags (TuxBadge kind="tag"), admonitions (TuxAlert), inline callouts, blockquotes, Q&A, code, diagrams, MDC, forms, tables, search, A–Z nav, sidebar widgets, signups, big stats, factoid rows. Surfaces the catalog's easiest-to-miss component names so newcomers don't ship duplicates.
  • TuxCodeMaroon (app/components/TuxCodeMaroon.vue) — institutional emergency alert banner. TAMUS's Code Maroon is the mandatory emergency-notification system; Rellis Campus (where TTI lives) routes through https://rellis.tamus.edu/emergency/detailsUrl defaults to that for TTI consumers, override for non-Rellis sites. Three severities (alert/warning/info) with hard-coded colors that don't theme (no tti-dark / tti-hc override — visual recognition matters more than palette consistency during emergencies). Defaults to non-dismissible per institutional convention; pass dismissible + v-model to allow dismissal. Sticky-position support via sticky prop. Pulsing siren icon respects prefers-reduced-motion. Demo at /components/code-maroon.
  • Catalog mop-up — five last components closing out every AggieUX catalog entry relevant for the three target consumers. Shipping these makes the system feature-complete for the marcom reveal:
    • TuxLinkSlab (app/components/TuxLinkSlab.vue) — full-width horizontal band of prominent links. The "footer-of-section navigation" pattern; distinct from TuxLinkList's multi-column grouped list. Three tones (plain / neutral / maroon).
    • TuxSidebarBlock (app/components/TuxSidebarBlock.vue) — generic sidebar widget wrapper. Eyebrow + maroon-underline title + content slot. Three variants (default / bordered / filled). Compose any content inside — lists of NuxtLinks (auto-styled), TuxDescriptionList, prose, etc.
    • TuxQACollection (app/components/TuxQACollection.vue) — long-form Q&A editorial pattern. Always expanded — designed to be read top-to-bottom. Companion to TuxAccordion kind="faq" (which is collapsible for scanning). Maroon "Q." marker + bold question + flowing answer prose, with optional "see also" link list per item.
    • TuxSignupFeature (app/components/TuxSignupFeature.vue) — newsletter signup block. Heading + dek + bordered email input + uppercase action + consent line. Three tones, three style variants. Self-contained.
    • TuxAlphaNav (app/components/TuxAlphaNav.vue) — A–Z jump bar for directory and glossary pages. Two modes: anchor (sets window.location.hash for in-page jumps) or emit (filter-in-place via v-model). Letters not in the dataset render dimmed and disabled.

    All five use container queries per ADR 0007. The catalog's "Ideas not yet shipped" section is now empty — every aspirational entry the AggieUX kit catalogued has shipped.
  • Composition examples — three "what does this enable" pages assembling 9–13 Tux* components each into real-shape surfaces:
    • /examples/pecan-dashboard (app/pages/examples/pecan-dashboard.vue) — IT-facing index overview. TuxBreadcrumbs, TuxPageHeader (with media slot), TuxFactoid, TuxAlert, TuxTreemap, TuxFilterPanel, TuxSearch, TuxBadge, TuxPagination, TuxDescriptionList, TuxSectionHeader, TuxCallout. (13 components.)
    • /examples/research-landing (app/pages/examples/research-landing.vue) — public-facing program hero. TuxPageHeader (hero + maroon), TuxFactoid, TuxBlockquote (drop-cap), TuxMediaSlab (split), TuxIconFeature, TuxCardSlab, TuxNewsCollection, TuxTestimonial, TuxCTA, TuxLinkList. (10 components.)
    • /examples/tti-ai-studio-session (app/pages/examples/tti-ai-studio-session.vue) — LLM/agent session view. TuxBreadcrumbs, TuxPageHeader, TuxAlert (compliance), TuxSectionHeader, TuxCallout, TuxDescriptionList, TuxFactoid, TuxAccordion, TuxCommandPalette. (9 components.)
    • /examples (app/pages/examples/index.vue) — the index page. Cards on the landing page promote it.
  • ADR 0007 (docs/adr/0007-container-queries-over-viewport-media-queries.md) — captures the WHY behind the container-query convention. Cross- references the gitignored CLAUDE.md and the design/components.md guidance section that enforce it for human + AI contributors.
  • #extra slot on TuxFooter — for inline left-side content alongside the version + © (high-contrast toggle in the style guide; system-status indicators or session counts in product consumers).

Changed

  • Style-guide chrome eats its own dogfood. The repo's app.vue no longer hand-rolls the header lockup or footer — it uses <TuxIdentity level="center" superhead="Texas A&M Transportation Institute" name="tti-ux"> for the header, <TuxFooter> for the app-level strip, and <TuxSubfooter> for the mandatory TAMUS legal block. The high-contrast toggle moves into TuxFooter's new #extra slot. Net effect: the live style guide is now a real example of itself, and any rough edges in the chrome components surface in the place they're most visible.
  • Container queries replace viewport media queries on layout-shifting components (TuxPageHeader, TuxMediaSlab, TuxCardSlab, TuxCTA, TuxNewsCollection, TuxBreadcrumbs). Each component declares its own container-type: inline-size + named container, then uses @container <name> (min-width: …) for layout breakpoints. Why: with the old viewport-based queries, a component embedded in a 600px demo wrapper inside a 1200px viewport would think it had room for a two-column layout it couldn't fit. With container queries, breakpoints fire on the component's own width — so the same component renders correctly whether it's at full page width, inside a sidebar tile, or inside a narrow article column.
  • Hero typography uses clamp() with cqi units for fluid scaling. Hero-rhythm titles, slab titles, news titles, and CTA titles now scale smoothly with the container width instead of jumping at a breakpoint. e.g. font-size: clamp(2rem, 1.2rem + 4.5cqi, 3.5rem) on the hero page-header title.
  • TuxTreemap tooltip flips on canvas edges instead of overflowing. When the cursor is past the canvas midpoint, the tooltip anchors to the cursor's opposite side using right/bottom instead of left/top. Removes the bug where hovering near a corner sent the tooltip off-screen.

Added

  • Catalog completion + tier 2/3 product batch — seven components closing out the AggieUX catalog backlog and unblocking the tier 2/3 product surfaces:
    • TuxContactCard (app/components/TuxContactCard.vue) — faculty / staff directory card. Portrait + credentials + role + typed contact rows (email/phone/web/office/location, with auto mailto: and tel:). Vertical or horizontal layout.
    • TuxPageHeader extended — same component, three new dimensions: tone="neutral|maroon" for backgrounded panels, rhythm="hero" for landing-page sized headings, #media slot for two-column hero treatments, #actions slot for trailing CTAs. Existing call sites unchanged.
    • TuxMediaSlab (app/components/TuxMediaSlab.vue) — full-bleed hero band, "big photo moment". Overlay or split layout, three heights, three placeholder tones.
    • TuxCardSlab (app/components/TuxCardSlab.vue) — full-bleed band of media-forward cards. The "browse our programs" pattern. Three aspect ratios, 2/3/4-up grid, whole-card click w/ corner-drop hover.
    • TuxPagination (app/components/TuxPagination.vue) — page-number controls. Native Vue (not UPagination) — squarified active page, ellipsis truncation, optional status line, configurable sibling/boundary count.
    • TuxCommandPalette (app/components/TuxCommandPalette.vue) — global ⌘K jump bar. Native <dialog> (free focus trap + scrim + ESC), grouped commands w/ live filter, keyboard navigation, action or navigation per command. Single instance at app root.
    • TuxTreemap (app/components/TuxTreemap.vue) — squarified hierarchical-size visualization, PECAN's headline chart. Pure SVG implementation of the Bruls-Huijsen-van Wijk algorithm — no viz library dependency. Click to drill, breadcrumb to zoom out, log- scaled maroon ramp, hover tooltip. 4 unit modes (bytes/count/percent).
  • Marketing surface batch — five components that round out the marketing/landing surface coverage. Marcom's eventual review now sees every common content block, not gaps:
    • TuxPhotoGrid (app/components/TuxPhotoGrid.vue) — uniform image grid. kind="photo" (4:3, captions, full color) or kind="logo" (1:1, grayscale-on-hover wall). Auto-fits columns, placeholder gradients for design mocks before real assets land.
    • TuxCaptionedMedia (app/components/TuxCaptionedMedia.vue) — single inline image (or video frame via #media slot). Four aspect ratios (16:9 / 4:3 / 1:1 / 3:4), three alignment treatments (full / wide / right-floated for prose).
    • TuxNewsCollection (app/components/TuxNewsCollection.vue) — institutional news-index pattern. Stacked list (thumb left, copy right) for /news landing pages, or grid (thumb above, 2 or 3 cols) for hub-page "recent news" sections. Dates render as <time> with ISO datetime.
    • TuxIconFeature (app/components/TuxIconFeature.vue) — the classic "our services" / "focus areas" block. Icon-in-tinted-circle + headline + body + CTA. Three tones cycle (maroon/navy/gold). Grid or list layout.
    • TuxLinkList (app/components/TuxLinkList.vue) — categorized resource list. "For sponsors / for partners / for students" footer-of-section pattern. Optional descriptions per link, auto-detected external arrows, featured: true for maroon left-bar emphasis.
  • Editorial breadth batch — four components that round out the marketing/content surface coverage so marcom's eventual review sees a finished system rather than a partial scaffold:
    • TuxBlockquote (app/components/TuxBlockquote.vue) — standalone editorial pull quote. Two layouts (centered with rules above/below; magazine-style drop-cap) and three style variants. Companion to TuxCallout (which is the inline aside).
    • TuxTestimonial (app/components/TuxTestimonial.vue) — attributed quote collection with portrait + name + role. Grid (cards, 2 or 3 cols) or row layout. Circular gradient portrait placeholders cycle through maroon/navy/gold; pass image for real photos.
    • TuxAccordion (app/components/TuxAccordion.vue) — FAQ + publication disclosure. Native <details>/<summary> (zero-JS, perfect a11y). kind="publication" switches to citation rhythm with italic title + meta line. single mode for mutually-exclusive groups.
    • TuxDescriptionList (app/components/TuxDescriptionList.vue) — term / definition pairs as native <dl>. editorial emphasis for marketing surfaces (event details, exhibit captions); data emphasis for product metadata (PECAN file detail, classifier specs).
  • Tier 1 navigation + chrome batch — five components that unblock shipping PECAN, tti-ai-studio, and tux's own surfaces:
    • TuxSearch (app/components/TuxSearch.vue) — branded inline search bar with attached uppercase action button. Two sizes (regular 60px / slim 51px), real working v-model, custom action label/icon. Demos at /components/search.
    • TuxFilterPanel (app/components/TuxFilterPanel.vue) — left-rail facet panel for list pages. Collapsible facet groups via native <details> (zero-JS, perfect a11y), checkbox lists with counts, applied-filter chips, Clear all. v-modeled as flat Record<string, string[]> for easy wiring to OpenSearch / TanStack. Demos at /components/filter-panel.
    • TuxIdentity (app/components/TuxIdentity.vue) — institutional header lockup. Two kinds (lockup / text-only) × two orientations (horizontal / stacked) × three hierarchy levels (institution / center / department). Replaces the aspirational header rhythm work. Demos at /components/identity.
    • TuxFooter + TuxSubfooter (app/components/TuxFooter.vue, app/components/TuxSubfooter.vue) — paired closer for every TTI surface. TuxFooter is the slim app-level compliance strip (version + © + utility links); TuxSubfooter is the mandatory TAMUS legal strip (Texas A&M University System requirement). Per AggieUX guidance the subfooter content is fixed — only the institute name + address + year are configurable. Demos at /components/footer.
  • Earlier in this Unreleased cycle: four editorial/data Tux* components driven by the same product needs:
    • TuxBigStat (app/components/TuxBigStat.vue) — single oversized metric with three sizes (lg/md/sm), three tones (maroon/gold/neutral), and style-variant-aware numeral face. Demos at /components/big-stat. Replaces the aspirational BigStat from design/components.md.
    • TuxCallout (app/components/TuxCallout.vue) — pulled-aside editorial accent (fact / stat / quote) with style-variant left rule (gradient / stacked-bars / diagonal-hash). Demos at /components/callout.
    • TuxBreadcrumbs (app/components/TuxBreadcrumbs.vue) — page-depth navigation. Home icon, italic intermediates, pipe-rule separators that collapse to chevron under 35rem. Demos at /components/breadcrumbs. Replaces the aspirational PathCrumbs from design/components.md.
    • TuxCTA (app/components/TuxCTA.vue) — big promotional block, three tones, two-column at ≥48rem with action slot. Demos at /components/cta.
  • Three style variants — .style--bold / .style--elegant / default — CSS scopes in tux.css plus a live /style-variants page demoing side-by-side. .style--bold rebinds --font-display to Work Sans and introduces the stacked-bar section signature + .dot-grid accent + .card-static--bold rectangular top-bar variant. .style--elegant rebinds to Georgia and introduces the diagonal .hash-pattern + dotted maroon borders + .card-static--elegant. Default style is implicit (no class needed) and renders the Oswald + maroon-hairline rhythm via .section-header.
  • Four-family lane tokens in tokens.css--font-body (Open Sans), --font-display (Oswald), --font-bold (Work Sans), --font-elegant (Georgia). Additive — legacy --font-sans (Public Sans) unchanged.
  • TuxFactoid component + /components/factoid demo page — the institutional "by the numbers" block. Numeral face changes per variant (Open Sans 700 / Work Sans 800-italic / Georgia italic), three densities (3/4/5-up at 96/72/56px). Replaces the aspirational <BigStat> from design/components.md.
  • /preview page + public/preview/ — 28 standalone HTML specimen cards (typography, color, spacing, components, brand motifs) served from /preview/*.html and indexed via iframe grid. Each loads /colors_and_type.css directly — framework-free — so designers can lift one into a deck without pulling Nuxt. colors_and_type.css and fonts/ mirrored into public/ for portable use.
  • /kits page + public/kits/ — five static reference kits carried from the portable design-system download: aggieux (~57-family component catalog), pecan / tti-docs / tti-ai-chat (three application-shape demos showing tux primitives composed into product surfaces), and slides (deck-stage slide system). All five render in vanilla CSS + React via Babel-in-the-browser. Treat as frozen design reference — the Tux*.vue components are authoritative when they disagree.

Changed

  • Renamed app/assets/css/aggieux.csstux.css — completes the aggieux → tux rename. nuxt.config.ts and all comments/blurbs in components, pages, ADRs, and design docs updated to match. The aggieux name now refers exclusively to the upstream Texas A&M MarCom AggieUX kit (snapshot in reference/aggieux/, copy in public/kits/aggieux/, sync scripts in scripts/sync-aggieux*.mjs) — not to our own design system. ADR 0004 file renamed to 0004-tux-tokens-separate-from-nuxt-ui-theme.md.
  • Renamed design/aggieux.mddesign/tux.md — the system is named tux (Aggie + UX); the older aggieux.md filename was a holdover from the initial PECAN extraction. Content also adopts the four-family typography rule (Open Sans / Oswald / Work Sans / Georgia + JetBrains Mono) and documents the three section styles (default/bold/elegant) — superseding the earlier Public Sans-as-default framing. Public Sans remains loaded by @nuxt/fonts for legacy components.
  • Theme toggle split — the header toggle is now light ↔ dark only (tti ↔ tti-dark). High-contrast mode (tti-hc) moved to a dedicated footer button, because WCAG AAA's line weights read as "broken" in a casual theme cycle and users shouldn't be pushed through an accessibility mode as part of aesthetic preference. See ADR 0006 — supersedes the three-way cycle from ADR 0005 (the palette itself stays).

Added

  • Initial scaffold (2026-04-24) — Nuxt 4 runnable style guide, extracted from PECAN's frontend so tti-ux can serve as the source of truth for downstream Nuxt apps.
  • 7 Tux* components wrapping Nuxt UI primitives where TTI branding requires deviation:
    • TuxAlert — 8 admonition variants (note/tip/info/important/success/ warning/danger/compliance) with left-bar treatment.
    • TuxBadge — 5 shapes (tier/status/tag/count/default).
    • TuxButton — single intent prop for primary/secondary/ghost/destructive.
    • TuxCard — static or linked; linked has corner-drop hover signature.
    • TuxModal — editorial rhythm (eyebrow + gold-bar title).
    • TuxSectionHeader — aggieux ALL-CAPS heading with maroon underline.
    • TuxTable — maroon-wash header, auto status-cell rendering via TuxBadge.
  • 3 themestti (default), tti-dark (warm-charcoal dark), tti-hc (WCAG AAA high-contrast). Three-way cycle in the header toggle.
  • Style-guide pages:
    • / — landing with foundations + components grid.
    • /tokens — brand palette, semantic roles, status colors, maroon ramp, shadows, radii.
    • /typography — heading utilities, type scale, inline treatments.
    • /motion — spacing ramp, duration tiers, corner-drop demo, Transition example.
    • /components + /components/{alert,badge,button,card,modal, section-header,table} — per-component demos.
    • /forms — inputs, textarea, select, radio, checkboxes, switch, slider, chip input, date, validation + focus-ring notes.
    • /patterns — empty state, loading skeleton, table-state cycle, confirmation flow pointer, admonition stack.
  • Placeholder tuxedo SVG logo — maroon jacket, gold bow tie, white shirt front, charcoal frame. Needs iteration with a polished mark.
  • Design docs (design/) — aggieux.md, components.md, palette.md, tokens.json — carried from PECAN.
  • Dev server binds :3030 — avoids collision with PECAN and docs-tti-tamu-edu on :3000.

Known gaps

  • No npm publish yet — downstream apps must consume via file:../tti-ux and extends: ['tti-ux'] in their nuxt.config.ts.
  • No git remote yet — local repo only; will push to ttitamu/tti-ux when the empty remote exists.
  • PECAN still ships its own copies of the 5 Pecan* wrappers. Layer consumption wiring is a separate follow-up.
  • Style Dictionary pipeline for design/tokens.jsontokens.css isn't wired; tokens.css is hand-maintained for now.
  • warning Nuxt UI color = Tailwind amber; a TTI-gold-anchored palette would back it properly — inherited TODO from PECAN.