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,FormsandToolingare gone as groups. Their entries redistribute: composed pages to Kits, Forms to a nested branch under Components, Accessibility and Contrast audit to Foundations.- New
Kitsgroup — "where do I look for an assembly of things." Composed examples, Patterns, and the frozen static reference designs. Designnarrows 11 → 7 by re-homing, not hiding.components.mdnow leads the Components group,chart-foundations.mdthe Visualizations group,compositions.mdthe Kits group. Every doc keeps a sidebar home and its/design/<slug>URL; only ownership moved./previewrelabelled Specimens in the sidebar. Route unchanged.
Added — Install pages for the kit/ tier (2026-09-01)
/install— one page for all nine framework targets, withdesign/kit-pipeline.mdas 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, renderingkit/powerbi/README.mdverbatim 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.
TuxExamplegains apowerbitab, pushed conditionally exactly assourcealready was.tableauslots in the same way.- Wired on chart-bar, chart-line, chart-area, chart-scatter (the
chart-cartesianfragment) and data-table (table-chrome), imported fromkit/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-kitby the existingdesign/*.mdglob. 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 inkit/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/scsshave no pages either — so this sets a pattern those can follow instead of being a one-off. Note/reportswould 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 BIvisualStylesemitter 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 — whichaudit:tokensflags. §2 now maps each role to the existing token and its Power BI counterpart. - Records one deliberate divergence — web gridlines use
--surface-borderat 50% opacity, the Power BI theme usessurface.border-subtle, because a theme'sgridlineColorhas no alpha channel — and one defect:TuxChartLinestyles axis ticks--text-mutedwhileTuxChartBaruses--text-secondary. Both clear AAA, so it is a consistency bug, not an accessibility one.--text-secondaryis named canonical; movingTuxChartLineis 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$schemaURL and format-version literal, emitted tokit/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(undergitIntegration/, notitem/report/),definitionProperties/2.0.0,versionMetadata/1.0.0.npm run verify:pbirre-checks all of them against the live endpoints.kit/powerbi/tti-theme-hc.json— third emitted theme, from the existingtti-hctokens. 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,tableExchrome and cartesian-chart chrome, in four lanes: statictti/tti-dark/tti-hc, plusthemedwhose colours bind to DAX measures.kit/powerbi/pbir/tmdl/+pbir/dax/— the in-report light/dark switch: aThemeModetable and 30TuxThemeColorsmeasures 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 livevisualContainer/2.9.0schema with its five remote$refs resolved. Includes the Dark/Light tile slicer (mode: 'Basic'andorientation: "1D"— both required) with cross-pagesyncGroup.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, withbuild:pbirwired intobuild: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
_generatedkey. The theme schema isadditionalProperties: falseover exactly 42 named properties, so every theme v1 emitted was technically invalid. Provenance moved tokit/powerbi/README.md. background(dark)#15100F→#221F1F. v1 mapped it tosurface.page, but Power BI'sbackgroundis the visual fill; it is nowsurface.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, abaseThemepin (Fluent2-CY26SU08—CY24SU06is two generations stale), the structural set (firstLevelElements…fourthLevelElements,secondaryBackground,shapeStroke,disabledText,hyperlink,visitedHyperlink), a brand-anchored divergent ramp, andvisualStylesfor 26 visual types. - Exports
generate(), sotests/tux-kit-targets.test.tscan byte-lock the output.build-powerbi-theme.mjswas 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)
atlas—Atlas,https://atlas.tti.tamu.edu,audience:"entitled", placed besidelandscape(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.version2 → 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.version1 → 2. New roster order:tti-www→tti-my→tux→landscape→tti-code.ai-studioremoved from the roster (owner call). The/launch/ai-studiointerstitial, 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 nokind:"desktop"entry left, that law is dormant;tests/useTuxApps.test.tsnow asserts the zero-count so the dormancy is explicit rather than a vacuously green test.tti-myadded —my.TTI, the internal site athttps://my.tti.tamu.edu. Shippedaudience:"public": the site enforces its own SSO, andauthenticatedis 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-wwwrepointed to the apexhttps://tti.tamu.edu(verified HTTP 200 with zero redirects;www.301s to it) and renamedTTI.audience:"entitled"remains ADVISORY upstream —app/app.vuerenders<TuxUtilityCluster current="tux" />with noentitledprop, so ux.tti.tamu.edu showspublictiles 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. Seedesign/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 + anAlldictionary for dynamic lookup.kit/react/tux-tokens.ts— per-theme resolved token maps for React/TS apps outside the Nuxt layer, plustuxVar()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 undersettings.custom.tux.- All three come from
scripts/build-framework-targets.mjs, which sharesbuild-tokens.mjs's selector builder and resolves var() chains, aliases, and washcolor-mixto literals per theme (dark washes correctly compute over the teal anchor). Wired intonpm run build:kit;tests/tux-kit-targets.test.tslocks 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/reactships@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.npmrcmaps the@ttiscope to the Forgejo registry (the same line consumers add). - Port-writer bot specced (kit-pipeline.md):
tux-port-botservice 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.ymlruns on every merge to main touchingTux*.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--checkmode 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:tokensnow 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@ttiscope to.npmrc, depend on@tti/tti-ux, and useextends: ["@tti/tti-ux"]innuxt.config.ts. The oldgithub:ttitamu/tti-ux#vX.Y.Zpin 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:
| Component | Before | After |
|---|---|---|
TuxFactoid | density: 3|4|5 | columns: 3|4|5 |
TuxBetaRibbon | tone: preview|beta|dev | kind (same values) |
TuxTabs | intent: default|bold | variant (same values) |
TuxChartGauge | band intent: ok|warn|alert | band tone: success|warning|error (band/fill CSS hooks renamed to match) |
TuxCodeMaroon | severity: alert|warning|info | tone: error|warning|info (--alert classes now --error) |
TuxTestimonial | color | tone (same values) |
TuxConfirmDialog | variant: danger | removed — use destructive |
TuxConversationList / TuxSuggestionChips | emit pick | emit select |
TuxCitations / TuxInlineCitation | emit open | emit select |
TuxStatComparison / TuxMapLegend | layout: stack | layout: stacked |
TuxFigureCaption / TuxTableCaption | kind (free-form label) | label |
TuxRichTextEditor | duplicate bare update emit | removed — 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-mixpercentages 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-tooltipshell +--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-primarycontrols (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.tsenforces the wash ladder and freezes raw color literals per component at an exact-budget ratchet (sanctioned chrome documented inline);scripts/audit-tokens.mjsno longer blanket-exempts Tailwind prefixes the app also owns (the hole that let--radius-fullship 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. Deadvar(--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-body700 + 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-bodytoken instead of a pasted stack.tests/tux-typography.test.tsnow guards all of it in CI. - Elevation system actually adopted. All hand-rolled box-shadows
and legacy
--shadow-sm/md/lgcomponent 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-focustwo-ring. --radius-fullfinally exists. Six components referenced it before it was ever defined — an invalidvar()that rendered suggestion chips, removable chips, info labels, and context meters square instead of pill. Token added (9999px), literal9999pxsites 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-primarywith a--surface-pagepaint-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 dogfoodsTuxActivityTimeline 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-partTuxX · TUXform.
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,slatevariant), equal-interval bins with the ranges printed in the legend, honest hatchednullcells ("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);normalizeswitches the y axis to share-of-samples for side-by-side comparisons; family tooltip/keyboard contract (band-mode roving cursor viauseTuxChartHover). Demo at/visualizations/chart-histogram.TuxActivityTimeline— editorial vertical event timeline (the audit's top component gap): maroon spine, mono tabular timestamps, gold-ringedcurrentnode, eyebrow-weightheadinggroup rows, semantic tones shared with the badge/alert families,denserail posture,#item/#trailingscoped 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 totuxChartScale, behavior-locked bytests/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 underprefers-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
TuxRichDataGridvirtualized big-list mode (owner-requested) — passvirtualized(+ optionalvirtualRowHeight, 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 foundation —
vitest.config.ts(the file never existed;npm testran bare) with a two-tier layout: pure tests stay in the node environment, mounted tests opt into thenuxtenvironment 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 andaria-labelledbywiring, and TuxAnnouncementBanner's localStorage-backed dismissal memory. Contributor guidance in components.md now listsnpm testamong 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 realrole="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,hoveremit,tooltipprop, 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-ringtoken (masked by fallbacks) and a brand-accent ring onTuxTreemap— that either double-drew on the universal shadow or suppressed it withoutline: 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 —TuxTreemapcells now use the themed--focus-ring-outerinstead of brand-accent) andforced-colorsblocks (system colors by design). Form-control:focusborder 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-tableremoved.** 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 aserializeroption so migrated components keep their exact wire format) anduseTuxClipboard(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-virtualstays — kept deliberately to back TuxRichDataGrid's virtualized big-list mode (owner-requested, landing separately).
TuxChartScatterkeyboard model: roving cursor instead of per-point tab stops. Every dot was previouslytabindex="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 bytests/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 driftedceilno 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-8everywhere (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-tonecustom property set by sharedtux-chart-tone--c1..8classes (app/assets/css/tux-chart-palette.css) — ~135 duplicatedvar(--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--cNclasses as consumer styling hooks. TuxChartGeographicper-kind code split — the fivekinds 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.tssplit intotexas-outline/texas-outline-polygon/txdot-districts/tx-metros(data byte-identical;texas.tsremains as a compat re-export barrel) andscripts/build-geo.mjsnow 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
TuxExampleHTML-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 inapp/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/TuxChartGaugeserialized unroundedMath.sin/Math.cosresults into arcdand 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 viatoFixed(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 (itshoverToneClasshelper predated the shared tone classes). tux-audit a11ygated on the wrong dependency — the dispatcher requiredpuppeteer, butaudit-a11y.mjsruns 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 checksjsdom; help text and README corrected to state the honest per-command dependency split.Math.random()element ids replaced withuseId()—TuxFocusView'saria-labelledbytitle id was generated by aMath.random()computed (an SSR hydration mismatch when rendered open, and a re-roll risk); now a stableuseId().TuxRuleBuilder's internal group child now derives rule/group ids from a per-instanceuseId()seed + counter instead ofMath.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 packagefiles— git-dep consumers previously couldn't even see the kit the README sells.kit/env/brand.env(npm run build:brand-env) — 71 flat resolvedTUX_<THEME>_<GROUP>_<TOKEN>=#hexkeys 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:kitchains 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.mjsgeneralized: target dirs via args/TUX_AUDIT_DIRS, token definitions harvested from the layer AND the consumer, extra namespaces viaTUX_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 a11ydelegate 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). Politerole="status"live region; error tones renderrole="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-toastis the live demo. Owns its own item state rather than piggybacking Nuxt UI's toaster so the semantics and motion are tux-controlled; migration fromuseToast()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-radiusbinds to--radius-md, aligningU*corner geometry with Tux chrome. - The four family fonts are now Tailwind utilities
(
font-body/display/bold/elegant). TuxButtonre-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/borderhooks (which must point at tokens — consumeraudit:tokensruns 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.vueoverride. - TuxSiteNav + TuxAppFrame publish
--tux-nav-heighton<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 withTTI_UX_DEMOS=1) — docs-tti deletes itspages:extendworkaround. - Dark-bridge tolerance: the Nuxt UI
.darkbridge now matches any*-darkcolor-mode value (not justtti-dark) and honors atux: { 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-fillsurfaces, in all three themes. The collab-stack's--tux-on-brandinsight made canonical: fixed-maroon surfaces must never use--text-inverse(it flips dark in dark mode). Adopted byTuxCTA,TuxFooter,TuxPageHeader,TuxAnnouncementBanner(replacing 10 hardcoded#fffdeclarations) and registered as the measured pair on/contrast-audit, so AAA CI now gates the real token. Tailwind alias--color-text-on-brandadded.--surface-border-subtle— the hairline-divider step below--surface-borderconsumers kept inventing as--border-subtle(light: neutral-150; dark: #2E2B2A; hc: deliberately === border).- Spacing scale —
--space-2/8/12/16/24/32/40/48/64join the existing 0/4/10, closing the "spacing is un-tokenized" gap against tux.md principle 4. - Migration table (kit/README.md +
audit:tokensfailure 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
warningalias 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 localwarning: "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/.subheaduse--font-body(Open Sans) — previously--font-sans(Public Sans, the "legacy" face per tux.md). .heading--displayuses--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-sansremains defined (self-hosted) but is deprecated — marked in tokens.json, tux.md, and globals.css.- Dead
@nuxt/fontsdependency removed (module was already out of nuxt.config; fonts are self-hosted viafonts.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.prerenderblock (crawlLinks +failOnError: false) is now fenced behind an is-root-project check, soextendsconsumers stop inheriting the style guide's prerender behavior. Landscape can delete itsnitro:configreset 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].vuetitle map (which also gains the missingroadmapandvisual-language-evolutiontitles).compositions.md§Suite chrome — the sectionTuxUserMenu/TuxUtilityClusterdocblocks 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;
TuxButtonre-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/componentsindex 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 ownTuxUserMenu+TuxUtilityCluster), 3 fromcomponents.md(backfilled:TuxRailNav,TuxUserMenu,TuxUtilityCluster).tests/tux-catalog.test.ts— catalog ↔ filesystem ↔ routes ↔components.mdinvariants in theuseTuxApps.test.tsmold. 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 testnow 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);useTuxAppsadded 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.vuecounts (~100/~95) corrected;components.md"~70 components" corrected and phantom references annotated (TuxPhotoCard→ roadmap;TuxStatusToast→ ships v1.8.0);roadmap.mdship-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 · identityin law-fixed DOM order; registry-fed waffle; theme toggle announces viarole="status". - TuxUserMenu — the suite's one identity affordance:
cluster+rail-footermounts, 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, reservedpresentationprop (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_TOKEN→LANDSCAPE_TOKEN). Historical records keep the old name.
Fixed
- TuxSiteNav focus restoration (A2c): both
outline: nonesuppressions removed — keyboard focus is distinguishable from hover again (WCAG 2.4.7) and survives forced-colors. - Five undeclared
@tiptapdependencies declared (v1.6.1);postinstallguarded 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 oftier/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.tostill wins when both are set.- Attribute forwarding —
TuxCardis a multi-root component (its three branches), so Vue couldn't auto-inherit fallthrough attrs. AddedinheritAttrs: false+ explicitv-bind="$attrs"on each root so consumers can reliably passclass/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-labelon bare SVG shapes (<rect>/<circle>) is prohibited without a name-supporting role; addedrole="img"to labeled data / interaction shapes.TuxDiagrammermaid output is legalized via a post-processor; its loading skeleton gotrole="status". TuxFileDropzone+/forms/file-upload— converted therole="button"drop zone wrapping a focusable<input type="file">(criticallabel+nested-interactive) to a native<label>wrapper: one focusable, named control, no nesting.TuxSuggestionChips— movedrole="listitem"off the<button>onto a wrapping element (aria-allowed-role).TuxCodeMaroon<aside role="alert">→<div role="alert">;TuxSplitPanedetail<main>→<section role="region">(no duplicate / nested main);TuxRichDataGridempty<th>→ visually hidden text;TuxAbstract/TuxAcknowledgmentshardcoded<h4>→ configurablelevelprop (heading-order).TuxCardCarousel— dropped the redundant outerregionlandmark (Nuxt UI'sUCarouselalready is one) and named that region instead.TuxTocshowcase 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
ariaLabelprops 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/aggieuxcontent wrapped in a<main>landmark (region); icon-only buttons on app-frame / swipe / text-field givenaria-label(criticalbutton-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 source — audit: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 missinglang— addedhtmlAttrs: { lang: "en" }tonuxt.config.tsapp.head. axe'shtml-has-langrule (serious impact) had been failing on every page.TuxDocsSidebarwas rendering<aside role="navigation">, which axe'saria-allowed-roleflags — 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.
TuxMobileFrame—platform="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:widthprop 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:toolbar—format·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 vialowlight(covers ~30 popular languages viacommon) · 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 viaturndownis deferred to a follow-on — the:sourceFormatprop is the planned extension point for when a consumer surface stores markdown.)
Full-screen: overlay mode that pins the editor to the viewport viaposition: 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'shandleKeyDownand emitted assave. Host wires to persist.
Typography substitutions: smart quotes, em dashes, ellipses (Tiptap Typography extension).
Undo / redo: disabled-state derived fromeditor.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 rootGrouptree. Provides the field catalog + max-depth to recursive children viainject.TuxRuleBuilderGroup— recursive internal child (same pattern asTuxTreeNodeinsideTuxTree). 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'smaxDepthprop (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 + optionalsourceline 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; honorsprefers-reduced-motionwith a static fill. Visual chrome matchesTuxArtifact(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-embedwith three demos (Linear default · Notion loading · Figma fixed-chrome)- the MCP three-tier display taxonomy reference.
Conventions doc updated —
design/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. Defaultmode="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 asTuxTooltip'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), plusauto.bodyprop covers prose;#bodyslot opens the surface to richer markup (lists, data blocks, form fields).#actionsslot renders into a footer with a top border; bare<button>elements get compact chrome via:deep(button); add thetux-popover__action--primaryclass 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 todesign/components.mdConventions 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-modeledCommentThread[]. Each thread groups a root comment + replies, has anopen/resolvedstatus, and supports@mentiontokens (auto-detected on render, brand- accent color). Authors are passed in as aRecord<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; explicitsize="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:
TuxShortcutsHelpshipped into the catalog: row added todesign/components.md, entry added to the/componentscard grid andapp.vuesidebar nav, new showcase page at/components/shortcuts-helpbuilt on theTuxCommandPaletteshowcase template (combos vs sequences, native<dialog>setup,defineShortcutswiring).TuxTreeNodeandTuxDocsSidebarNodeannotated as internal children ofTuxTreeandTuxDocsSidebarindesign/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 gates — npm 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.vue—iframeEl.value?.completeis anHTMLImageElementproperty, not an iframe one. Replaced withcontentDocument?.readyState === "complete"(still null for cross-origin maps, which fall through to theloadevent).TuxReactionBar.vue— replacedcounts[r.key] !== undefined && counts[r.key] > 0with(counts[r.key] ?? 0) > 0so TS can narrow through the indexed access.TuxChartLine.vue— addedas [number, number]cast to theyDomain[0, 1]fallback so the destructuredlo/hiaren'tnumber | undefineddownstream.TuxChartScatter.vue— narrowed type predicate from(x): x is stringto(x): x is "positive" | "negative" | "flat"to satisfy "predicate's type must be assignable to parameter's type".TuxTable.vue— relaxedreadStatussignature from{ original?: Record<string, unknown> } & Record<string, unknown>to{ original?: unknown }so it acceptsRow<unknown>from UTable's slot.TuxAppFrame.vue— replaced ternaryemit(action === … ? … : …)with explicitif/elsebranches per emit name so TS can match the discrete tuple overloads.TuxCardCarousel.vue— addedpropscapture +carouselItemscomputed that casts throughunknownto satisfy UCarousel's strictCarouselItem[]prop without losing the genericTfor the#itemslot.TuxConfirmDialog.vue— narrowedconfirmIntentreturn type to"primary" | "destructive"; mapped the "warning" dialog variant to the primary button intent sinceTuxButton'sIntentdoesn't include "warning" and the warning context lives in the dialog chrome, not the button.TuxFormField.vue— kept kebab-case slot bindings (eslintvue/attribute-hyphenationrequires it) + added a comment pointing consumers at the string-key destructure pattern ({ 'aria-describedby': x }).app/pages/components/forms-wrapper.vue— switched theTuxFormFieldslot destructures to string-key syntax to match Vue's literal type extraction foraria-*props.app/pages/components/rich-data-grid.vue—rows.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.vuepages so we can use real TS annotations to narrowBand.intentand tuple-typedSeries.band/Ref<[number, number]>without violating ADR-0010's macro-extractor constraint. See ADR-0011 for the prescribed pattern.
Architecture decision:
- New
ADR-0011 — Sibling
*.demo-data.tsmodules for typed page data. Supplements ADR-0010 with the escape hatch when type-narrowing needs collide with the macro-extractor constraint — JSDoc workarounds don't suffice for literal narrowing, so extract to a sibling.tsmodule instead.
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].vuetitle 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 gainstux-mount-instagger 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-inopt-in class — immediate children fade + 4px slide up on mount (220ms). Stagger via--tux-mount-stagger-indexper child..tux-mount-in--sectionlarger variant for section-level entrances (320ms)..tux-mount-fade+.tux-mount-scalefor 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) andtti-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.pyartifact 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-inentrance 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 ahoverevent 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-motion—reducecollapses 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:rangeto 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-linegains 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.mdconsolidates 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 belowminSlice%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 withTuxBigStatfor 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 forTuxBigStatinstead.
- optional tone bands using status tokens) and
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 anidis provided. Folds to single-column below tablet. Selection is URL-bound (consumer's responsibility); empty-state defaults toTuxEmptyStateif#emptyslot 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 carriesaria-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. Followschart-foundations.mddoctrine.
New doc:
design/tauri-bindings.md— companion to platform-awareness. Enumerates which TUX components call which Tauri APIs (window controls inTuxAppFrame, share sheet inTuxArtifact, file dialog inTuxCodeBlock, notifications inTuxStatusToast), 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.platformso CSS branches without each component asking JS. SSR-safe defaults.
- primary modifier + system accent post-hydration. Sets
app/assets/css/tux-scrollbar.css— per-platform scrollbar styling auto-imported viaglobals.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. Honorsenv(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. ComposesUDropdownMenu. 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. Honorsprefers-reduced-motion.
Enhancements:
TuxKbd— reads fromuseTuxPlatform()instead of inline navigator-platform sniff. RendersSuperon Linux +Winon Windows. Three platform symbol tables.TuxModal— addssizeprop (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-trailingcover 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 futureTuxMcpEmbed+TuxCardCarouselbuild when tti-ai-studio integrates MCP tool output. Findings + carry-forwards inreference/figma-cache/mcp-apps-for-claude/NOTES.mdand 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— wrapsUTabs. 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 bothTuxTabsHorizontalandTuxTabsVerticalroadmap entries (one component, prop covers both).TuxTooltip— wrapsUTooltip. Optional title + hairline rule; tuned max-width (~22ch); shortcut glyphs viakbds. For richer panels, consumers useTuxTeachingPopover(onboarding) orUPopoverdirectly.
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
nounPluraloverride.
- pageSize; page-size picker auto-snaps to page 1 on change. Naive
pluralization with
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. Honorsprefers-reduced-motionautomatically (renders explicit button instead of auto-fetching);keyboardFallbackprop forces the button on for accessibility-critical surfaces.
Feedback + comparison:
TuxReactionBar— light-touch acknowledgement strip. Default trio (helpful · question · disagree) with full configurability viareactionsprop. 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). Optionalseries[].previousfor dashed prior-period overlay, optionalseries[].bandfor confidence bands, optional markers, auto-derived screen-reader summary. Showcase at/visualizations/chart-linewith five examples including aTuxChartFrame-wrapped editorial variant.
Accessibility documentation (4 pages):
/accessibility/skip-to-content/accessibility/focus-model/accessibility/contrast-matrix— USWDS-style top-10 token-pair summary with per-theme ratios/accessibility/breakpoints— six-breakpoint scale + container-query preference + 200% zoom
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
#tocslot in/reports/web-frameshowcase. 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'stocprop) 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):
TuxComposer—cancelableprop +cancelLabelprop +@cancelemit. 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—#headerMenuper-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 withUDropdownMenu,UPopover, or a fullTuxFilterPanel. Absorbed from the Data-table-design Figma kit + Snow Dashboard. New showcase section on/components/rich-data-grid.app/layouts/sidebar.vue—#asideslot for an optional right rail. Fixed-width (asideWidthprop, default18rem), sticky-scrolling next to the main panel, hidden undermdviewport. Absorbed from Snow Dashboard's notifications + activities rail. Thelandscape-dashboardexample 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-upTuxCardgrid; 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 (
TuxArtifact→TuxCardCarousel→TuxArtifactinsideTuxSlideover/TuxModal).
Tier 2 — new design doc:
design/chart-foundations.mdskeleton. Captures the doctrine futureTuxChartLine/TuxChartBar/TuxChartAreawill 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 aroundUCarousel(embla under the hood); cards are the consumer's responsibility —TuxCard,TuxArtifact,TuxContactCard, or a bare<article>all work. Two render modes:itemsarray +#itemscoped 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.vue→landscape-dashboard.vue. Route is now/examples/landscape-dashboard. - Renamed
public/kits/pecan/→public/kits/landscape/; kit slug + label updated inapp/pages/kits.vue. - Prose updated across
README.md,design/components.md,design/roadmap.md, ~30 showcase pages underapp/pages/, 10 component JSDoc blocks (TuxDescriptionList,TuxCodeBlock,TuxFilterPanel,TuxPagination,TuxTeachingPopover,TuxTreemap,TuxSearch,TuxSiteNav,TuxRichDataGrid,TuxFooter), top-of-file comments innuxt.config.ts/app/app.vue/app/layouts/sidebar.vue, and thepublic/kits/aggieux/*.jsxreference catalog (defaultproductNameknobs 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.vueto 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#asideslot follow-up onsidebar.vuewaits 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 todesign/components.mdConventions section. Reaffirmed against the Empty State Illustration Kit absorption (NOTES); the fivekindpresets 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,hideSingletondefaults true (N=1 renders nothing), optionalloop. Position rendered as text for SR announcement.TuxInlineCitation— academic-style superscripted[N]pill in body text. Hover or click revealsUPopoverwith title + URL + excerpt + score. One source per pill (not Vercel's "+N" aggregation); composes with the existingTuxCitationsfooter 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 theactionsprop emit named events; custom actions via#actionsslot. Standalone — consumer wraps in container (inline, sidebar aside, full-page).TuxCodeBlockgained a download button beside the existing copy. Resolves filename from adownloadNameoverride → thefilenamebasename →code.{ext}from a 25-language extension map. Both buttons share a hover-revealed__actionscontainer.TuxChatMessagegained a#header-trailingslot 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.
TuxFilterPanelrefactored to consumeTuxRemovableChipfor 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 standardfor=attribute; optionalrequiredshows the maroon*.TuxTeachingPopover— onboarding / guided-tour tooltip. Distinguished fromUTooltip(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.onBrandvariant for high-emphasis announcements.
Added — Sidebar app-shell layout + demo example
app/layouts/sidebar.vuerewritten to compose Nuxt UI 4'sUDashboardGroup+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 viadefinePageMeta({ 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.jsongained abreakpointblock:xs(0) /sm(640) /md(768) /lg(1024) /xl(1280) /2xl(1536), with$descriptionper breakpoint documenting the semantic trigger (what surface state changes at this width). Values match Tailwind v4 defaults somd:/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@themeblock 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:
compactboolean 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.kindpreset 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 yetno-results— query / filter returned nothingnot-found— resource doesn't exist (or moved)no-permissions— auth-gated; user lacks accessfirst-run— onboarding; "welcome, get started"
Expliciticon/title/descriptionprops 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-statedemonstrates each preset.- Template dedup as a side-effect. The earlier
compactaddition only worked on the with-card branch — theno-cardbranch 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 honorcompactandkindidentically.
Added — Standard composition conventions
design/components.mdgained a new Conventions section with two documented standards:- Chat-message actions (Vercel AI Elements absorption) — the
standard
TuxChatMessage#toolsicon 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.
- Chat-message actions (Vercel AI Elements absorption) — the
standard
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.tsnow setscomponents: [{ path: resolve(layerDir, "app/components"), global: true }].global: trueregisters each component viaapp.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— theactionsprop's loop calledemit(a)wherea: ArtifactAction. Replaced with anonAction(a)switch dispatch over the literals.TuxBranchNav—emit(delta === 1 ? "next" : "prev")couldn't narrow. Replaced withif (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"]inapp/assets/css/tokens.cssnow pins both--color-maroon-400and--color-maroon-500to#500000. The 500 override is the visible fix — solid Nuxt UI primitives (UButton, UBadge, etc.) in HC mode were rendering#5c0025from:rootinstead of the ADR-stated#500000. The 400 override is defensive (Nuxt UI's.darkpath 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) watchescolorMode.valueand toggles.darkon<html>when the preference istti-dark.tti-hcis 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 intti-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-400in[data-theme="tti-dark"]retuned from brick-wine#702C28to 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-fillfor 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 withnpm run sync:fonts.app/assets/css/fonts.cssdeclares 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.tsdrops@nuxt/fontsfrommodulesand itsfontsblock; prependsfonts.cssto thecssarray.
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-deepretuned 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 = #702C28override 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#7a1233still rang loud against the teal accent (mid-saturation magenta hue). Reuses the existing--map-seq-maroon-2value — 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-brandrepointed 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 TuxModal — TuxSlideover 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-corridorcurve over--motion-baseduration. The panel itself opts intodata-tux-overlay(Batch J easing) anddata-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 / closedside: "left" | "right" | "bottom"(default"right")size: string— CSS length for width (left/right) or height (bottom)title+eyebrow— header heading + optional eyebrow aboveshowClose: boolean— top-right close button (defaulttrue)closeOnBackdrop: boolean— dismiss on scrim click (defaulttrue)
- 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? }. Passtofor internal nav (renders<NuxtLink>),hreffor external, or leave both off for a pure toggle/select row (renders<div role="button" tabindex="0">to sidestep Tailwind v4's@layer basebutton reset, which would otherwise win over scoped CSS via layer cascade order). Setmono: trueon 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 viapadding-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 persistenceshowGuides: boolean— 1px sand guide lines under expanded branches (defaulttrue)v-model:selected— currently-selected node IDariaLabel: 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--activeso 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 nav —
Enter/Spacetoggle+select,ArrowRightexpands a collapsed branch,ArrowLeftcollapses an expanded branch. Up/down navigation uses native focus order through the row elements (which are all focusable). - Demo page
/components/treeshows three example trees: sitemap (route links + badges), corpus / filesystem browser (mono leaf labels, row-count badges), and a BI dataset hierarchy (per ADR-0009) withv-model:selecteddriving a focused-field side panel. - Nav wiring —
app/app.vuesidebar,app/pages/components/index.vuecatalog, anddesign/components.mddoctrine 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>; passas="div"when the parent already provides the landmark. Replaces three duplicate ~130-line.prose-tux :deep(…)blocks that lived inline inapp/pages/changelog.vue,app/pages/design/[doc].vue, andapp/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-styleContentSurroundat article bottom (prev / next sibling design docs, sorted by slug). The surround cards opt into Batch J'sdata-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./typographyrefresh — 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'sdata-tux-elevation="rest". heading--elegantsection 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--elegantapplied to the same heading text. TuxProsewrapper 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.
- Family roster — five articles, one per token
(
- Nav wiring —
app/app.vuesidebar,app/pages/components/index.vuecatalog, anddesign/components.mddoctrine table all gain theTuxProserow.
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 samedefineShortcutskey 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 oneTuxKbdgroup) or sequence (no modifier, rendered asTuxKbd "then" TuxKbd). Built on<dialog>for free focus trap + scrim + escape semantics, same anatomy asTuxCommandPalette. Opt-indata-tux-overlay+data-tux-elevation="overlay"so it picks up the Batch J easing curve and shadow.TuxCommandPaletterefactor — replaces the hand-rolledkeydownlistener withdefineShortcuts({ 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 kbdscoped 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, withpaletteGroupsderived from the existingnavTreeso a single source of truth drives sidebar nav and palette search.defineShortcutsat the shell wires:meta_k— open the global palette (handled byTuxCommandPaletteinternally)/— open the global palette (GitHub idiom)?— toggle the shortcuts-help overlayg-c/g-t/g-d/g-h— sequence shortcuts to/components,/tokens,/design/tux,/
/components/command-palettedemo — adds:disable-hotkey="true"on the page's local palette instance so⌘Konly fires the global one. Replaces the page's hand-rolled<kbd>markup with<TuxKbd>.- Nav wiring —
app/app.vuesidebar tree,app/pages/components/index.vuecatalog table, anddesign/components.mddoctrine table all gain theTuxKbdrow.
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.cssgains aBATCH Jsection with six rules:- J.1 —
transition-timing-function: var(--ease-survey)default forbutton, a, input, select, textarea, [role="button"], [role="tab"], [role="option"], .tux-card, .card-linked*(gated byprefers-reduced-motion). Opt-indata-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. Thehovervalue also appliestranslateY(-1px)for the canonical lift. - J.3 —
.tux-hoverliftutility: one-class opt-in for the rest → hover transition pairing. - J.4 —
.tux-mark/.tux-mark--lg/.tux-mark--xlfor sizing and tinting identity-primitive<symbol>s from/identity-primitives.svg(already shipping inpublic/). Default 16×16, currentColor =--brand-primary;--mark-sizeand--mark-colorare 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.
- J.1 —
design/visual-language-evolution.mdgains the matchingBatch J — 2026-05section documenting each rule, the opt-in pattern, and the Nuxt-vs-skill path note (assets/identity-primitives.svgin the skill payload →/identity-primitives.svghere).- 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 inapp/assets/css/tokens.css. The mapping--font-body-bold→--font-boldreconciles 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*.jsxdemo 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 pages —
AggieCatalog(registers every new section),AggieButtonsAlerts,AggieCards,AggieChrome,AggieIconLink,AggieMenus,AggieNewsContact,AggiePageHeaders,AggiePages,App.jsx. index.htmlupdated to register all 64 JSX modules via<script type="text/babel">tags.AggieDisclosure.jsx(Batch 10: accordions / publication accordion / Q&A) andAggieDisclosures.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.jsxships 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.htmlrewritten to registerStructuredOutput.jsxand to demonstrate the new primitives — the assistant Message in the demo conversation now renders anInlineCarouselof six citation cards, aResponseCardfor the Linear ticket draft (withtoolName,status,footer), and a compactInlineCardfor the related runbook. Title kept astti-ai-chatto 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 fromdocs-it-tamu-edu). - Consumers:
tti-reporting(ttitamu/tti-reporting) currently snapshots tokens intocore/themes/tokens.jsonand ports the Aggie Viz shell intocore/ttiviz/SHELL_SPEC.md. Once tti-ux owns these, those become reference snapshots that follow tti-ux releases. docs-tti-tamu-edustops 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.vueis 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 undertti-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
exclusiveTopLevelprop onTuxDocsSidebarmakes 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 (defaultfalse) so existing consumers (PECAN docs shells, etc.) aren't surprised. - Long-name truncation: leaf-link labels now share the
__labeltruncation class with summary rows. Component names likeTuxAnnouncementBannerwere blowing past the 240px lane and — combined with the CSS spec coercingoverflow-xtoautowheneveroverflow-yis non-visible — surfaced a horizontal scrollbar inside__nav. The root cause was__sublist'sdisplay: gridauto-sizing the column tomax-content; pinning togrid-template-columns: minmax(0, 1fr)lets the column shrink below max-content so the ellipsis actually kicks in.__navalso gets an explicitoverflow-x: hiddenas belt-and- braces against future inner-layout drift. - Sticky sidebar shell: the shell wrapper in
app.vueis nowmd:sticky md:top-[57px] md:self-start md:max-h-[calc(100vh-57px)]on desktop withoverflow-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-startopts out of the flex parent's default stretch so sticky can actually take effect; mobile keeps itsfixed inset-y-0 top-[57px]slide-in shell unchanged. - Content width: shell content cap raised from
max-w-5xl(1024px) tomax-w-6xl(1152px). Modest bump — leaves room for the future right-railTuxTOCand 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: LinkedInlinkedin.com/company/texasa-mtransportationinstitute, Facebookfacebook.com/ttitamu, Instagraminstagram.com/ttitamu/, YouTubeyoutube.com/ttitamu, Twitter/Xtwitter.com/TTITAMU. Previously we shipped a staleTTIVideoChannelYouTube link andTTITAMUS(with S) for several others. - Threads added (sixth social, matching production). Lucide
doesn't ship a Threads glyph, so
TuxFooternow accepts an optionalsvg?: stringfield onSocialLinkas an alternative toicon— pass raw<svg>markup. Threads renders via the Simple Icons path (CC0). Force-setfill="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 attsl.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 upstreamtamus.eduURLs 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
taglineprop still defaults to that string for back-compat; the shell passestagline=""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-linkrule. - © line now linked. Two new optional props on
TuxFooter:copyrightText(full override of the default "© {year} {name}" rendering) andcopyrightHref(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 hardborder-b/border-rTailwind utilities on the shell header and sidebar are replaced with 1px pseudo-elements drawn vialinear-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'sstudio-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.catalogCountconstant 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/exitretained 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; oldsm/md/lgare 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,expandedfor row body,bulk-actionsto 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 asvalue ± CIwhen a column declaresciKey; 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 toTuxTreemap. 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.mjsruns at build time only — pulls us-atlas (npm; TIGER/Line 1:10m) and the official TxDOT MapServer feature service, simplifies viatopojson-simplify, projects withd3-geoAlbers(Texas) /d3-geoAlbersUsa(US), emits three TS modules underapp/assets/geo/(texas.ts,texas-counties.ts,us-states.ts). Outputs are checked in; re-run vianpm run build:geowhen 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-1through--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 tonitropack(open-redirect via wildcard route rules + proxy scope bypass),uuid(missing buffer-bounds check in v3/v5/v6), andbasic-ftp(unbounded multiline FTP control buffer DoS, transitive via Puppeteer's proxy-agent chain).potraceremoved fromdevDependencies. The remaining five vulns were a single root cause counted across thephin@2.x → @jimp/core → @jimp/custom → jimp → potracechain. Potrace was a build-time-only utility (the SVGs it produced ship inpublic/); the scriptscripts/png-to-svg-logo.mjsnow 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 forTuxDataTable,TuxRichDataGrid(main table) andTuxChartFrame,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-aspirationalTuxMapEmbedMapbox/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/CNAMEforux.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+filesinpackage.jsonso 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
devDependenciestodependencies—@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, withfile:../tti-uxkept 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#trailingslot — renders inside.tux-site-nav__bar-innerafter 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 (utilityNavprop).PrimaryNavItem.to— now valid alongsidedropdown/megaMenu. When set, the trigger LABEL becomes a realNuxtLink(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 matchestoexactly OR is a strict descendant path OR matches any descendant item'sto. 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--activemodifier: 9% maroon background tint + 2px maroon left rail (viabox-shadow: inset) + bold label +aria-current="page".- Auto-close on navigation via
watch(() => route.fullPath, …). Same--activeshape applied to plain primary-nav links viaTuxSiteNav.isPlainLinkActiveso all three primary-nav variants share one visual language.
Fixed
- Active-state on
?query=and#hashroutes —TuxDropdown.isItemActive,TuxMegaMenu.isItemActive, andTuxSiteNav.isPlainLinkActivewere comparingroute.path === targetwhich strips both the query and hash. Items like/policies?tab=approvalsand/admin#fleetnever 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 sharedapp/utils/nav-active.tshelper 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=/#anchordon't change which section the operator is in. Also picked up byTuxDocsSidebarso doc anchors work the same way.
- Trigger + item clickability under hover-open state —
TuxDropdown/TuxMegaMenu/TuxSiteNav/TuxFooterwere rendering navigable elements via<component :is="triggerComponent">v-bind="linkAttrs(item)"wheretriggerComponentresolved aNuxtLinkfrom 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
TuxMegaMenupanel sizing — wasposition: absolute; left: 0; right: 0; max-width: 80rembut anchored to the trigger<li>(which hadposition: relativefor TuxDropdown's per-trigger panels). Result: panel inherited the narrow trigger box; columns stacked vertically becauseauto-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 atmin(48rem, calc(100vw - 2rem))with amin-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'sapp/, not the layer's, so~/assets/css/tokens.cssin the layer's nuxt.config breaks consumers when theyextendsthe layer. Now usesdirname(fileURLToPath(globalThis._importMeta_.url))→resolve(layerDir, "app/assets/css/..."). Same fix applied toapp/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 flipwarning: amber → goldin theirapp.config.tswithout a localtokens.cssshim.
Documentation
- Internal-tool footer exception. The "one footer component,
vary content via
columnsandsocialprops" 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 ownPecanOpsFooter(~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
/accessibilitypage with the formal conformance statement: WCAG 2.2 AA target; color contrast verified at AAA across all three themes (light, dark, high-contrast)./contrast-auditpage renders every contrast-risk surface in three themed columns simultaneously (one perdata-theme).scripts/audit-contrast.mjsuses 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 whenAUDIT_LEVEL=AAAis set (CI default).audit-contrast.ymlworkflow 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-filltoken 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.
- light
- Tailwind v4
@themecascade workaround: re-bound every--color-*alias inside[data-theme="tti-dark"]and[data-theme="tti-hc"]because Chrome freezes@themevariable references at:rootscope. Without this, theme overrides on raw tokens didn't propagate to Tailwind utility classes.
Changed — BREAKING
TuxFooteris now the unified institutional footer. Earlier releases shipped three separate components for the page anchor (slimTuxFooter,TuxMarketingFooter,TuxSubfooter); they're collapsed into a singleTuxFooterthat 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 forcolumnsandsocialto 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).
- Migration: replace
Added
/changelogpage — 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.0version pill — surfaced in the header lockup and the welcome eyebrow, sourced frompackage.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'serror.vueor inline via theinlineprop.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). Honorsprefers-reduced-motion: reduceautomatically — collapses to a static tint, no movement, per the roadmap spec. SR-only "Loading…" label on arole="status"region; decorative shapes arearia-hidden.TuxStepper(app/components/TuxStepper.vue) — numbered-circle multi-step indicator. Status auto-derives fromcurrentIndex(done · active · todo); per-steperroroverride. Two orientations (horizontalcollapses toverticalbelow ~30rem container width via container query, not viewport). Optionaltoper step renders the node asNuxtLinkfor jump-back navigation. Renders as<nav aria-label>witharia-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 fromTuxAlert(page-body admonition) — this is chrome that sits above the page header, full-width, persists across routes. Dismissal persists inlocalStoragekeyed byidprop; emitsdismissfor 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 (positionprop). Decision (accepted/rejected/custom) persists inlocalStorageand emits viadecisionevent so consumers can gate analytics. Surface only; per-category UI lives in the#categoriesslot — 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 onTuxAnnouncementBanner 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.
/formsis 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-field—UInput+UTextareastate matrix: default · focus · filled · disabled · readonly · error · with helper · with counter · auto-resize · trailing button · leading icon./forms/select—USelectMenuwith descriptions (TAMUS classification tiers), searchable combobox, creatable values, multi-select./forms/choice—UCheckboxgroup with select-all + indeterminate parent;URadioGroupwith descriptions and inline orientation; "when to reach for which" decision section./forms/date-picker— nativeUInput type="date"with helper, min/max bounds, range-input pattern, plustime/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-coverageentries: "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-uxlink the other consumers use.TuxChatMessage(app/components/TuxChatMessage.vue) — one conversation turn.role="user"orrole="assistant"; the assistant variant sits on--surface-sunkenso 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 aTuxAlert variant="compliance"via the#scopeslot 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
/reportssection — 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 viauseHead. Hides chrome markeddata-print="hide"; paginates withdata-print-break="before|after|avoid".- Reports landing (app/pages/reports/index.vue) — overview tiles, scope explainer (what's a report vs a viz).
- New
/visualizationssection — 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. NewposterSrcprop 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 (imagePNG/JPG,svgvia<object>,htmlfor htmlwidgets in a sandboxed iframe). Source-line caption forsessionInfo()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
TuxChatMessageassistant avatar (replaces the earliertxitalic 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/roadmapwas reachable but unlinked); collapsed the redundantTuxSiteNav/TuxDropdown/TuxMegaMenutrio (all three pointed at the same demo page) into a singleTuxSiteNaventry.
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 asTuxReportFrame(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).
- **Why a new component instead of just composing TuxPageHeader
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.9absolute). The SVG carries an auto-derivedaria-labeland matching<title>summarizing the trend ("Trend: 10 points, low 61, high 84, last 84 (+35.5% from first)"); passunits="$M awarded"to suffix the summary.- First native chart in the catalog. Shipped ahead of the
full
TuxChartBar/TuxChartLinefamily because it's small (no axes, no legend, no library) and the demand was already clear: pair withTuxBigStat/TuxFactoidfor KPI rows. - Demo at /visualizations/sparkline.
- First native chart in the catalog. Shipped ahead of the
full
TuxVizGrid(app/components/TuxVizGrid.vue) — small-multiples layout primitive for the Visualizations section. 2-, 3-, or 4-up panes ofTuxVizEmbed/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/TuxReportRPlotwere renamed toTuxVizEmbed/TuxVizRPlotmid-batch and the roadmap still carried the old names). TuxSparkline crossed off Priority B.
Renamed (since 1.0.0; not yet released)
TuxReportEmbed→TuxVizEmbed, demo route/reports/embed→/visualizations/embedTuxReportRPlot→TuxVizRPlot, 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
brandLockupprop — 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.pngfor TTI; sibling institutions override with their own white-on- transparent variant; passnullto 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).
- Three new public assets:
- 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>+ recursiveTuxDocsSidebarNode. 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 viaIntersectionObserver, smooth-scrolls on click, updates URL hash without triggering a full navigation. Pairs withTuxDocsSidebarfor 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
defaulttheme tobaseso 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 fromTuxExample(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 respectprefers-reduced-motion.@nuxtjs/mdcintegration — markdown rendering with Vue components inline. Configured innuxt.config.tswith 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/markdownshows 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 throughhttps://rellis.tamus.edu/emergency/—detailsUrldefaults to that for TTI consumers, override for non-Rellis sites. Three severities (alert/warning/info) with hard-coded colors that don't theme (notti-dark/tti-hcoverride — visual recognition matters more than palette consistency during emergencies). Defaults to non-dismissible per institutional convention; passdismissible+ v-model to allow dismissal. Sticky-position support viastickyprop. Pulsing siren icon respectsprefers-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 fromTuxLinkList'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 toTuxAccordion 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 (setswindow.location.hashfor 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.mdguidance section that enforce it for human + AI contributors. #extraslot 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.vueno 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#extraslot. 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()withcqiunits 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/bottominstead ofleft/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 automailto:andtel:). Vertical or horizontal layout.- TuxPageHeader extended — same component, three new dimensions:
tone="neutral|maroon"for backgrounded panels,rhythm="hero"for landing-page sized headings,#mediaslot for two-column hero treatments,#actionsslot 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) orkind="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#mediaslot). 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: truefor 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 toTuxCallout(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; passimagefor 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.singlemode for mutually-exclusive groups.TuxDescriptionList(app/components/TuxDescriptionList.vue) — term / definition pairs as native<dl>.editorialemphasis for marketing surfaces (event details, exhibit captions);dataemphasis 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 flatRecord<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 aspirationalBigStatfromdesign/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 aspirationalPathCrumbsfromdesign/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 intux.cssplus a live/style-variantspage demoing side-by-side..style--boldrebinds--font-displayto Work Sans and introduces the stacked-bar section signature +.dot-gridaccent +.card-static--boldrectangular top-bar variant..style--elegantrebinds 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. TuxFactoidcomponent +/components/factoiddemo 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>fromdesign/components.md./previewpage +public/preview/— 28 standalone HTML specimen cards (typography, color, spacing, components, brand motifs) served from/preview/*.htmland indexed via iframe grid. Each loads/colors_and_type.cssdirectly — framework-free — so designers can lift one into a deck without pulling Nuxt.colors_and_type.cssandfonts/mirrored intopublic/for portable use./kitspage +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), andslides(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.css→tux.css— completes the aggieux → tux rename.nuxt.config.tsand all comments/blurbs in components, pages, ADRs, and design docs updated to match. Theaggieuxname now refers exclusively to the upstream Texas A&M MarCom AggieUX kit (snapshot inreference/aggieux/, copy inpublic/kits/aggieux/, sync scripts inscripts/sync-aggieux*.mjs) — not to our own design system. ADR 0004 file renamed to0004-tux-tokens-separate-from-nuxt-ui-theme.md. - Renamed
design/aggieux.md→design/tux.md— the system is named tux (Aggie + UX); the olderaggieux.mdfilename 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/fontsfor 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— singleintentprop 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 themes —
tti(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-uxandextends: ['tti-ux']in their nuxt.config.ts. - No git remote yet — local repo only; will push to
ttitamu/tti-uxwhen 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.json→tokens.cssisn't wired;tokens.cssis hand-maintained for now. warningNuxt UI color = Tailwindamber; a TTI-gold-anchored palette would back it properly — inherited TODO from PECAN.