@flyos/design-system 1.7.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flyos/design-system",
3
- "version": "1.7.0",
3
+ "version": "2.0.0",
4
4
  "description": "FlyOS design system — shared components, directives, pipes, services, and models for Business App developers.",
5
5
  "keywords": [
6
6
  "flyos",
@@ -23,11 +23,11 @@
23
23
  // ADDITIVE ONLY. `tools/ds-compat` classifies re-valuing an existing theme
24
24
  // token as MAJOR (it forks the Native Federation shared singleton for every
25
25
  // remote), so nothing here may redefine a token another DS partial owns.
26
- //
26
+
27
27
  // Three tokens are deliberately ABSENT — they are the consuming app's BRAND and
28
28
  // every app already sets them in its own theme file:
29
29
  // --accent --accent-soft --focus-ring
30
- //
30
+
31
31
  // The other nine platform tokens (--danger* --warning* --purple --scrim
32
32
  // --glass-blur) USED to be absent too, on the assumption that the platform layer
33
33
  // supplies them. That assumption only holds when an app runs FEDERATED, where
@@ -36,7 +36,7 @@
36
36
  // commented out in both Circles and Thoughts — so `var(--danger)` resolved to
37
37
  // nothing and error text rendered colourless. Confirmed in a live browser, not
38
38
  // inferred: 9 of the 12 computed to the empty string on Thoughts standalone.
39
- //
39
+
40
40
  // They are now mirrored below at the SAME values `_theme-light/dark.scss` uses.
41
41
  // Mirroring (rather than picking new values) is what makes this safe under the
42
42
  // cascade: this file's `:root, html.light-theme` selector can tie with the
@@ -47,6 +47,107 @@
47
47
  // `_fly-theme.scss`, so `@use 'fly-theme'` is all a consumer needs.
48
48
  // ─────────────────────────────────────────────────────────────────────────────
49
49
 
50
+ // ── The platform TYPE layer (UX v2, S1.7–S1.10 fallout) ──────────────────────
51
+ // `_tokens.scss` owns `--font-family` / `--font-family-mono` and the `--font-*`
52
+ // role ramp (`--font-footnote`, `--font-caption1`, …). Like the two Nova
53
+ // partials below it was reachable only through `_fly-theme.scss`, i.e. only in
54
+ // the desktop shell.
55
+
56
+ // The type layer fails harder than the colour layers do, and it fails silently.
57
+ // The Nova form/overlay/content tasks landed 26 declarations across `lib/**` of
58
+ // the form `font: 600 13px/1.3 var(--font-family)` or `font: var(--nova-font-
59
+ // menu-item)`. A `var()` naming an undefined custom property is invalid at
60
+ // computed-value time, and in a SHORTHAND that invalidates the WHOLE
61
+ // declaration — weight, size and line-height all fall back to inherited, not
62
+ // just the family. Standalone that is a 13px control label rendering at the
63
+ // app's 16px body size inside a box hard-sized to 36/30/26px: a `fly-select`
64
+ // trigger, a `fly-form-field` label, a `fly-checkbox` label, a `fly-drawer`
65
+ // title. Nothing reported it because the shell renders every one of them
66
+ // correctly. Before this line the DS had ZERO `var(--font-family)` references
67
+ // in `lib/**`; the guard that now walks those files lives in
68
+ // `app-surface-nova-bridge.spec.ts`.
69
+
70
+ // Idempotent in the shell for the same reason as the two below: `_fly-theme.scss`
71
+ // already `@use`s `tokens` FIRST, Sass loads a module once per compilation, and
72
+ // the compiled entry is byte-identical either way.
73
+
74
+ // Standalone it adds 120 greenfield names, of which the three External Apps
75
+ // reference five. Four of those (`--font-title2`, `--system-blue`,
76
+ // `--system-indigo`, `--system-red`) were dangling references that now resolve.
77
+ // The fifth is `--font-family` itself, and it is the one behavioural change in
78
+ // this commit: Circles already loads the DS's own Geist from its
79
+ // `public/ui-fonts/geist.css` and is unaffected, but Thoughts and PPM write
80
+ // `var(--font-family, 'Roboto', …)` on `html, body` while bundling Roboto and
81
+ // NOT Geist. Both must bind `--font-family` to their own stack (or drop the
82
+ // indirection) before taking this DS release, or their body text falls through
83
+ // to a face they do not ship. Recorded here rather than worked around: the
84
+ // alternative — leaving the token undefined so their fallback keeps firing —
85
+ // is what dropped the 26 declarations above.
86
+ @use 'tokens';
87
+
88
+ // ── The Nova bridge (UX v2, S1.11) ───────────────────────────────────────────
89
+ // `_nova-tokens.scss` carries the Nova vocabulary — the `--w*` ink ramp,
90
+ // `--mat-*`, `--sys-*`, `--tint-*`, the `--glass2-*` quartet. `_fly-theme.scss`
91
+ // loads it, and the desktop shell is the only consumer that loads that entry.
92
+ // The three External Apps do NOT: Circles, Thoughts and PPM each `@use` THIS
93
+ // partial by bare name, and Circles keeps `// @use 'fly-theme';` commented out
94
+ // on line 1 of its `styles.scss` — the same fact this file's header records.
95
+
96
+ // So without the line below, the Nova names are undefined in precisely the
97
+ // builds that render this family on its own opaque paper. That matters for what
98
+ // comes NEXT rather than for anything today: at the DS 2.0 flip the values in
99
+ // this file are re-pointed at ramp steps (`--ink: var(--w95)`,
100
+ // `--line: var(--w18)`; the whole table is in `.workflow/plans/ux-refresh/
101
+ // notes/S1.11-app-surface-bridge.md`). A `var()` whose token is undefined makes
102
+ // the entire declaration invalid at computed-value time, so that flip would
103
+ // silently unset ink and hairlines for every standalone External App while
104
+ // looking perfect in the shell — the light-mode-only failure that
105
+ // `_shell-embed-bridge.scss` and `_ink-baseline.scss` were each written to end,
106
+ // arriving a third time through a different door.
107
+
108
+ // It is additive in both directions. In the shell it changes nothing at all:
109
+ // `_fly-theme.scss` already `@use`s `nova-tokens` first, Sass loads a module
110
+ // once per compilation, and the compiled entry is byte-identical either way.
111
+ // Standalone it adds greenfield names only — no Nova name is referenced or
112
+ // declared anywhere in Circles, Thoughts or PPM. It is also invisible to
113
+ // `tools/ds-compat`, which derives the token surface by scanning `src/scss/**`
114
+ // per file, not by compiling this entry.
115
+
116
+ // `app-surface-nova-bridge.spec.ts` holds the wire in place and proves that
117
+ // every `var()` this file emits still resolves inside the standalone bundle.
118
+ @use 'nova-tokens';
119
+
120
+ // The same argument, for motion. The app-surface components now reach for the
121
+ // Nova keyframes (`itemIn` / `riseIn` / `appTileIn`) and the `--nova-ease-*` /
122
+ // `--nova-duration-*` tokens, and those live in `_nova-motion.scss` — which,
123
+ // like `_nova-tokens.scss`, was reachable only through `_fly-theme.scss` and so
124
+ // only in the shell. Standalone, an `animation: itemIn …` naming an undefined
125
+ // keyframe is not an error: it silently does nothing, so a list that staggers
126
+ // in for a Core App would appear instantly in an External App with no gate
127
+ // anywhere reporting a difference. Bridging it here is what makes the two
128
+ // builds agree. Same idempotence: the shell already loads this module, so the
129
+ // compiled shell entry is unchanged, and standalone it adds one `:root` block,
130
+ // the keyframe inventory and the single `prefers-reduced-motion` block.
131
+ @use 'nova-motion';
132
+
133
+ // The same argument again, for the Nova TYPE roles — and the reason `tokens`
134
+ // above is not optional. Half the `--nova-font-*` table SNAPs onto the platform
135
+ // ramp by reference (`--nova-font-menu-item: var(--font-footnote)`,
136
+ // `--nova-font-body`, `--nova-font-tooltip`), so loading this partial without
137
+ // `tokens` would not fix anything: it would move the dangling reference one
138
+ // level down, where a component's `font: var(--nova-font-menu-item)` still
139
+ // resolves to an invalid value and still drops whole. The two travel together
140
+ // or neither is worth loading.
141
+
142
+ // This is the one `@use` in this file that the partial's own header argues
143
+ // against ("`@use`d exactly once, from `_fly-theme.scss` … never a second
144
+ // time"). That warning is about COMPONENT-level `@use`, which re-emits the
145
+ // `:root` block into each component's compiled stylesheet; this is a global
146
+ // entry, the same role `_fly-theme.scss` plays, and Sass still emits the module
147
+ // exactly once per compilation. Federated it is `_fly-theme.scss`'s copy;
148
+ // standalone it is this one. Never both.
149
+ @use 'nova-type';
150
+
50
151
  :root,
51
152
  html.light-theme {
52
153
  // ── Neutrals — text ──────────────────────────────────────────────────────
@@ -60,7 +161,7 @@ html.light-theme {
60
161
  // Label for a fill of `--ink` itself (the primary button). Distinct from
61
162
  // `--ink-inverse`, which is a FIXED near-white for coloured/gradient fills and
62
163
  // therefore wrong here: `--ink` flips per theme, so its label must flip too.
63
- //
164
+
64
165
  // CONTRACT — anything that redefines `--ink` MUST redefine `--on-ink`.
65
166
  // It must also be OPAQUE. The shell-embed glass remaps in Circles and Thoughts
66
167
  // set `--ink` to white and every surface token to a translucent veil; a
@@ -105,14 +206,22 @@ html.light-theme {
105
206
  --on-accent-soft: var(--ink);
106
207
 
107
208
  // 2. --accent-fill is a SOLID plate we own outright, so its ink is knowable
108
- // regardless of backdrop — always white. Badges/chips use this pair, so
109
- // their contrast cannot be broken by a host token override.
209
+ // regardless of backdrop — knowable FROM THE PLATE, not "always white".
210
+ // Badges/chips use this pair, so their contrast cannot be broken by a
211
+ // host token override.
110
212
  --accent-fill: var(--accent);
111
213
 
112
- // Aliased, not a second copy of the same white: "ink on a solid accent plate"
113
- // and "ink on a dark/gradient fill" are the same requirement, so they must
114
- // move together if the inverse ink is ever re-tuned.
115
- --on-accent-fill: var(--ink-inverse);
214
+ // FALLBACK RUNG ONLY. The accent-aware derivation at the foot of this file
215
+ // overrides this wherever relative colour syntax parses; this is what an
216
+ // older engine gets, and Nova's theme-invariant `--on-accent` (#fff) is the
217
+ // right answer for the platform violet in both themes.
218
+
219
+ // It is deliberately NOT an alias of `--ink-inverse` any more. Two tokens that
220
+ // happen to agree today are not the same requirement: "ink on a dark/gradient
221
+ // fill" is a constant, "ink on the accent plate" is a function of the accent.
222
+ // Aliasing them made the second one's contrast depend on a token that is not
223
+ // the plate. See the foot of the file for what it depends on now.
224
+ --on-accent-fill: var(--on-accent);
116
225
 
117
226
  // ── Interaction state layers ─────────────────────────────────────────────
118
227
  // Translucent veils layered OVER a surface for hover/press/selected instead
@@ -263,6 +372,7 @@ html.dark-theme {
263
372
  --ink-3: oklch(62% 0.005 250deg);
264
373
  --ink-4: oklch(45% 0.005 250deg);
265
374
  --ink-hover: oklch(85% 0.005 250deg);
375
+
266
376
  // Flips with --ink above: the primary button is a near-white pill here, so its
267
377
  // label goes near-black. Aliased to --bg (the darkest opaque surface in this
268
378
  // theme) rather than repeating its literal — see the contract on the :root decl.
@@ -324,17 +434,17 @@ html.dark-theme {
324
434
  // and each reference carries a hardcoded literal fallback, e.g.
325
435
  // `var(--label-primary, #1d1d1f)` or `var(--surface-card, #fff)`. 211 such
326
436
  // fallbacks exist across the library.
327
- //
437
+
328
438
  // Those literals are theme-BLIND: a single constant picked by whoever wrote the
329
439
  // component. Federated that never shows, because the desktop shell defines the
330
440
  // whole chrome family. Standalone, no one does — so a business app got whatever
331
441
  // constant each component happened to hardcode, in BOTH light and dark.
332
- //
442
+
333
443
  // Aliasing the chrome names onto the app-surface family fixes all 211 sites at
334
444
  // once, without touching a single component: the var() resolves through the
335
445
  // token and never reaches its literal. The targets are theme-aware, so these
336
446
  // follow light/dark automatically.
337
- //
447
+
338
448
  // SELECTOR IS LOAD-BEARING: this block is `:root` ALONE — deliberately not
339
449
  // `:root, html.light-theme` like the blocks above. The shell declares every one
340
450
  // of these under `html.light-theme` / `html.dark-theme` (specificity 0,1,1),
@@ -364,3 +474,108 @@ html.dark-theme {
364
474
  --surface-hover: var(--bg-hover);
365
475
  --surface-active: var(--state-selected);
366
476
  }
477
+
478
+ // ── Plate ink, derived from the plate (UX v2, S7.1a) ─────────────────────────
479
+ // `--on-accent-fill` labels a SOLID `--accent-fill` plate: `fly-detail-shell`'s
480
+ // active tab, `badge()`'s default, `fly-pagination`'s current-page pill.
481
+
482
+ // THE FAILURE IS THE EXTERNAL-APP ACCENT, NOT THE EMBED. A fixed near-white is
483
+ // right for the platform violet (5.25:1) and for the 2.0 `--accent-hi`
484
+ // (8.33:1), and catastrophic for an External App that remaps `--accent` to a
485
+ // light brand colour: white on a pale gold #F5C542 measures 1.58:1, on a cyan
486
+ // #22D3EE 1.76:1. Three tokens are deliberately absent from this file precisely
487
+ // because the accent is the consuming app's to choose (see the header), so this
488
+ // is not a hypothetical — it is what the seam is for. The label has to follow
489
+ // the plate's LUMINANCE, which means the plate is what it is computed from.
490
+
491
+ // WHAT S1.11 RECORDED HERE WAS WRONG, and the correction is the reason this is
492
+ // a derivation rather than the one-line repoint that note proposed. The claim
493
+ // was that `app-surface-on-glass` pinning `--ink-inverse` to a dark literal
494
+ // dragged `--on-accent-fill` (then an alias of it) down to ~2.5:1 on #9333EA.
495
+ // It never did. A custom property is substituted at the element that DECLARES
496
+ // it: `--on-accent-fill: var(--ink-inverse)` resolves at `:root`, and the
497
+ // resolved value is what inherits. Every shipped `app-surface-on-glass` call
498
+ // site is a component host far below `:root`, so it re-values `--ink-inverse`
499
+ // for its own subtree and cannot reach an `--on-accent-fill` that was computed
500
+ // above it. Measured in Chromium against the real compiled mixin: inside the
501
+ // mixin `--ink-inverse` is `oklch(18% 0.005 250deg)` while `--on-accent-fill`
502
+ // is still `oklch(99% 0 0deg)`, rendering 5.25:1 — identical to outside it.
503
+ // The same rule is why there is no generic `--on-plate` token; see below.
504
+
505
+ // WHY A LIVE `var()` CHAIN AND NOT A BUILD-TIME COMPUTATION. Sass cannot see
506
+ // the input. `--accent` is a runtime knob: `FlyThemeService.applyAccent` writes
507
+ // it as an INLINE style on `<html>` from tenant/user branding, and
508
+ // `isAccentColor` admits anything `CSS.supports('color', …)` does. Any value
509
+ // resolved at compile time is answering a question about a colour that does not
510
+ // exist yet. Same reason `--shadow-accent` is a `color-mix` and not a hex.
511
+
512
+ // MECHANISMS REJECTED.
513
+ // · `contrast-color(var(--accent-fill))` is CSS Color 5 and is literally this
514
+ // feature, but it is not yet carried widely enough to be the only rung — and
515
+ // it splits black/white on the same WCAG luminance the expression below
516
+ // computes, so it would buy a dependency rather than an answer.
517
+ // · `light-dark()` pairs on the THEME. An accent's lightness is independent of
518
+ // the theme (an app may set a pale accent in dark mode), so it answers a
519
+ // different question and fails on exactly the case that motivates this.
520
+ // · `color-mix(… var(--accent) N%, white)` scales lightness monotonically; it
521
+ // cannot invert polarity, so it cannot cross the light/dark accent boundary.
522
+ // · "External Apps override the token" moves the defect into every integrator,
523
+ // silently — nothing can gate an app that forgets. The token stays
524
+ // overridable as an escape hatch; it is not the contract.
525
+
526
+ // THE EXPRESSION. Relative colour syntax in `srgb-linear` exposes `r`/`g`/`b`
527
+ // as exactly the linearised channels WCAG's relative-luminance formula takes,
528
+ // so `0.2126r + 0.7152g + 0.0722b` IS that luminance — no approximation. An
529
+ // oklch-`l` split is the tempting one-channel version and it is not safe: `l`
530
+ // and WCAG luminance disagree by up to ~0.04 on saturated hues, which near the
531
+ // boundary picks the wrong pole and ships a ~3.7:1 label. `(0.1791 - Y) *
532
+ // infinity` clamped to 0..1 is a step function: 1 (white) below the threshold,
533
+ // 0 (black) above. 0.1791 is where white and black contrast a background
534
+ // EQUALLY, so the worst case over the entire sRGB cube is 4.58:1 — the AA floor
535
+ // here is arithmetic, not a review. The explicit `/ 100%` is load-bearing:
536
+ // relative colour syntax inherits the ORIGIN's alpha when the slot is omitted,
537
+ // and this file's header requires a counter-ink to be OPAQUE or it composites
538
+ // into its own fill. A tenant accent may legitimately carry one — `isAccentColor`
539
+ // admits any value `CSS.supports('color', …)` accepts.
540
+
541
+ // TWO RUNGS. Relative colour syntax is Baseline since Firefox 128 (2024-07);
542
+ // anything older keeps the `--on-accent` white declared above, i.e. today's
543
+ // platform-correct behaviour. `@supports` is what makes that a fallback at all:
544
+ // a custom property accepts almost any token sequence at PARSE time, so the
545
+ // usual declare-it-twice ladder does not work here — the later declaration
546
+ // always wins and the failure surfaces at the consumer as an unset `color`.
547
+ // The `infinity` keyword needs no rung of its own; every engine with relative
548
+ // colour syntax predates its own support for it.
549
+
550
+ // THERE IS NO GENERIC `--on-plate` TOKEN, and the substitution rule above is
551
+ // why. A plate that is not the accent — `-board`'s presence chips carry a
552
+ // per-user colour from `presenceColorFor`, bound inline per element — cannot be
553
+ // served by a `:root` token, because a `:root` declaration substitutes `:root`'s
554
+ // input, not the chip's. Tried and measured: with an `--on-plate` token declared
555
+ // here and its input set on the chip, every chip resolved to the `:root`
556
+ // fallback and nothing changed. A plate the DS cannot know has to be derived in the
557
+ // CONSUMING declaration, on the element that paints it, which is what
558
+ // `canvas-diagram` / `canvas-fishbone` now do. `$_plate-ink` is the one
559
+ // statement of the formula; `plate-ink.spec.ts` holds those copies to it, and
560
+ // evaluates this one at both accent poles rather than pattern-matching it.
561
+
562
+ // Both theme classes are listed rather than leaning on `:root` alone: a future
563
+ // redeclaration of `--on-accent-fill` inside `html.dark-theme` (0,1,1) would
564
+ // otherwise silently outrank a bare `:root` (0,1,0) override.
565
+
566
+ // A Sass-private string, interpolated once per channel: all three take the SAME
567
+ // switch, and written inline Sass would try to evaluate `0.2126 * r` as
568
+ // arithmetic and fail. So this is both the escape hatch and the single place
569
+ // the formula exists — it cannot drift between the channels or the tokens.
570
+ $_plate-ink: 'clamp(0, (0.1791 - (0.2126 * r + 0.7152 * g + 0.0722 * b)) * infinity, 1)';
571
+
572
+ // The probe colour is `rgb(0 0 0)`, not a hex, so the identical prelude can be
573
+ // copied verbatim into `design-system-board` — whose own gate bans bare colour
574
+ // literals anywhere in a component stylesheet, prelude and comments included.
575
+ @supports (color: color(from rgb(0 0 0) srgb-linear r g b)) {
576
+ :root,
577
+ html.light-theme,
578
+ html.dark-theme {
579
+ --on-accent-fill: color(from var(--accent-fill) srgb-linear #{$_plate-ink} #{$_plate-ink} #{$_plate-ink} / 100%);
580
+ }
581
+ }
@@ -27,13 +27,18 @@
27
27
  // plate ~47px wide) cut every tool's tip off inside its own column, and no z-index could
28
28
  // help — clipping is not a stacking question, and `position: fixed` on the pseudo would
29
29
  // still be trapped by those plates' `backdrop-filter` containing block.
30
- //
30
+
31
31
  // `FlyTooltipDirective` now owns the surface for BOTH `[flyTooltip]` and `[data-tooltip]`:
32
32
  // a `position: fixed` node parented to `<body>`, so it escapes every clip, flips placement
33
33
  // and clamps to the viewport. Components that write `data-tooltip` imperatively (the DS
34
34
  // icon button) list the directive in `hostDirectives`; templates with a static or bound
35
35
  // `data-tooltip` must IMPORT `FlyTooltipDirective` for the label to appear.
36
- //
36
+
37
+ // The surface itself (Nova's `--mat-tip-a`/`-b` material, opaque, no blur) lives entirely
38
+ // in the directive's injected `<style>` — see `fly-tooltip.directive.ts` for the recipe and
39
+ // why it is deliberately NOT a `glass.popover()` thin-blur surface (S1.8). Nothing here
40
+ // draws a tooltip; this selector only keeps the positioning context callers relied on.
41
+
37
42
  // Only the positioning context the attribute implied is kept, since callers may still hang
38
43
  // their own absolutely-positioned adornments (badges, dots) off a tooltip'd control.
39
44
  [data-tooltip] {
@@ -1,9 +1,32 @@
1
- @use 'vos-button-mixins' as *;
1
+ @use 'fos-button-mixins' as *;
2
2
 
3
- // ─── visionOS Button Utility Classes (shared with FlyOS desktop shell) ────────
3
+ // ─── FlyOS Button Utility Classes (shared with the desktop shell) ─────────────
4
4
  // Requires @use 'fly-theme' (or equivalent tokens) before this partial.
5
5
 
6
+ // ## The `.vos-` aliases are a deprecation window, not a style
7
+
8
+ // These three classes are the only PUBLISHED part of the old `vos-` prefix: External
9
+ // Apps write them in their own templates (measured: Circles authors `vos-btn` x21 and
10
+ // `vos-link` x4 across 6 files; Thoughts and PPM use none). Removing the old spelling
11
+ // is therefore a breaking change to this package's CSS surface — and a silent one,
12
+ // because a class that stops matching is not a build error in either repo, it simply
13
+ // renders unstyled.
14
+
15
+ // **And no gate would have caught it.** ds-compat extracts four things — the TS export
16
+ // surface, CSS CUSTOM PROPERTIES, CSS layers, and peer floors. It has no class-name
17
+ // extractor, so a renamed class is invisible to it: dropping `.vos-btn` outright still
18
+ // reports "federation-safe". The gate protects the token surface, not the class surface,
19
+ // even though the class surface is the one External-App TEMPLATES are written against.
20
+
21
+ // So `fos-` is canonical and `vos-` rides along as an alias, and the alias is doing the
22
+ // work the gate cannot.
23
+
24
+ // **Delete the `.vos-` halves at DS 2.0 (plan item S6.1b) — and only AFTER S6.2 lands
25
+ // the Circles migration.** Removing them first breaks Circles with no diagnostic.
26
+ // Verify against a grep of the Circles worktree, not of this repo.
27
+
6
28
  @layer designsystem {
29
+ .fos-btn,
7
30
  .vos-btn {
8
31
  position: relative;
9
32
  display: inline-flex;
@@ -60,11 +83,11 @@
60
83
  }
61
84
 
62
85
  &.platter::before {
63
- @include vos-platter-layers(inherit);
86
+ @include fos-platter-layers(inherit);
64
87
  }
65
88
 
66
89
  &:hover:not(:disabled, .selected, .active)::before {
67
- @include vos-platter-layers(inherit);
90
+ @include fos-platter-layers(inherit);
68
91
  }
69
92
 
70
93
  &.selected,
@@ -81,7 +104,7 @@
81
104
  pointer-events: none;
82
105
 
83
106
  &::before {
84
- @include vos-disabled-layers(inherit);
107
+ @include fos-disabled-layers(inherit);
85
108
  }
86
109
  }
87
110
 
@@ -104,6 +127,8 @@
104
127
  }
105
128
  }
106
129
 
130
+ // `.vos-btn-symbol` alias: see the deprecation note at the top of this file.
131
+ .fos-btn-symbol,
107
132
  .vos-btn-symbol {
108
133
  position: relative;
109
134
  display: inline-flex;
@@ -152,11 +177,11 @@
152
177
  }
153
178
 
154
179
  &.platter::before {
155
- @include vos-platter-layers(500px);
180
+ @include fos-platter-layers(500px);
156
181
  }
157
182
 
158
183
  &:hover:not(:disabled, .selected, .active)::before {
159
- @include vos-platter-layers(500px);
184
+ @include fos-platter-layers(500px);
160
185
  }
161
186
 
162
187
  &.selected,
@@ -173,7 +198,7 @@
173
198
  pointer-events: none;
174
199
 
175
200
  &::before {
176
- @include vos-disabled-layers(500px);
201
+ @include fos-disabled-layers(500px);
177
202
  }
178
203
  }
179
204
 
@@ -185,6 +210,8 @@
185
210
  }
186
211
  }
187
212
 
213
+ // `.vos-link` alias: see the deprecation note at the top of this file.
214
+ .fos-link,
188
215
  .vos-link {
189
216
  display: inline-flex;
190
217
  align-items: center;
@@ -6,6 +6,15 @@
6
6
  @use 'theme-light';
7
7
  @use 'theme-dark';
8
8
 
9
+ // ── Nova (UX v2) design language ─────────────────────────────────────────────
10
+ // Additive layer: new vocabulary only, nothing here re-values a token the three
11
+ // partials above already define (the colliding glass quartet is renamed
12
+ // `--glass2-*` per the D1 ruling). Canon: `skills/desktop-design-language.md`.
13
+ // `nova-glass` is mixins-only and is @used by components, not from here.
14
+ @use 'nova-tokens';
15
+ @use 'nova-motion';
16
+ @use 'nova-type';
17
+
9
18
  // The inherited `color` for themed documents. Emits a rule, not tokens, so it
10
19
  // lives apart from the theme partials — see its header for why the absence of a
11
20
  // baseline was the root cause of the recurring black-ink-on-glass bug.
@@ -1,28 +1,28 @@
1
- // visionOS button pseudo-element layers (used by _business-app-buttons.scss).
2
- // Kept separate from desktop-app glass mixins so Business Apps only pull button-related SCSS.
3
-
4
- @mixin vos-platter-layers($radius) {
5
- content: '';
6
- position: absolute;
7
- inset: 0;
8
- border-radius: $radius;
9
- pointer-events: none;
10
- background:
11
- linear-gradient(var(--btn-platter-lighten), var(--btn-platter-lighten)),
12
- linear-gradient(var(--btn-platter-dodge), var(--btn-platter-dodge));
13
- background-blend-mode: lighten, color-dodge;
14
- mix-blend-mode: screen;
15
- }
16
-
17
- @mixin vos-disabled-layers($radius) {
18
- content: '';
19
- position: absolute;
20
- inset: 0;
21
- border-radius: $radius;
22
- pointer-events: none;
23
- background:
24
- linear-gradient(var(--btn-disabled-lighten), var(--btn-disabled-lighten)),
25
- linear-gradient(var(--btn-disabled-dodge), var(--btn-disabled-dodge));
26
- background-blend-mode: lighten, color-dodge;
27
- mix-blend-mode: screen;
28
- }
1
+ // FlyOS button pseudo-element layers (used by _business-app-buttons.scss).
2
+ // Kept separate from desktop-app glass mixins so Business Apps only pull button-related SCSS.
3
+
4
+ @mixin fos-platter-layers($radius) {
5
+ content: '';
6
+ position: absolute;
7
+ inset: 0;
8
+ border-radius: $radius;
9
+ pointer-events: none;
10
+ background:
11
+ linear-gradient(var(--btn-platter-lighten), var(--btn-platter-lighten)),
12
+ linear-gradient(var(--btn-platter-dodge), var(--btn-platter-dodge));
13
+ background-blend-mode: lighten, color-dodge;
14
+ mix-blend-mode: screen;
15
+ }
16
+
17
+ @mixin fos-disabled-layers($radius) {
18
+ content: '';
19
+ position: absolute;
20
+ inset: 0;
21
+ border-radius: $radius;
22
+ pointer-events: none;
23
+ background:
24
+ linear-gradient(var(--btn-disabled-lighten), var(--btn-disabled-lighten)),
25
+ linear-gradient(var(--btn-disabled-dodge), var(--btn-disabled-dodge));
26
+ background-blend-mode: lighten, color-dodge;
27
+ mix-blend-mode: screen;
28
+ }
@@ -1,29 +1,29 @@
1
- // ─── Inherited-ink baseline ──────────────────────────────────────────────────
2
- //
3
- // Establishes the document's inherited `color`. Until this existed, NOTHING in
4
- // the shell ever set `color` on `html` or `body` — `_theme-light.scss` and
5
- // `_theme-dark.scss` declare custom properties only. So the inherited colour was
6
- // the user-agent default, **black**, and every component that says
7
- // `color: inherit` (fly-tree-nav rows, fly-breadcrumb's current crumb,
8
- // fly-search-input's typed text, fly-card's meta slot, and any consumer markup
9
- // that simply doesn't mention colour) rendered black ink on the smoked glass.
10
- //
11
- // That is why the "text is invisible in light theme" bug kept coming back app
12
- // after app: it was never really an app's mistake. There was no baseline to
13
- // inherit, so *forgetting* to set a colour was the failure mode, and the fix
14
- // kept being applied one component at a time. `--text-color` is white in BOTH
15
- // themes since the Liquid-Glass flip, so one declaration settles it everywhere.
16
- //
17
- // Scoped to the two theme classes on purpose — NOT `:root`. A business app
18
- // running STANDALONE renders on its own opaque paper (`--bg`, near-black
19
- // `--ink`) and does not load `fly-theme` at all; keying on the class means a
20
- // surface that never opted into a shell theme is never repainted white.
21
- //
22
- // A deliberate paper surface inside the shell (the documents editor) still
23
- // brings its own dark ink locally — a component rule beats this baseline, which
24
- // is exactly the intended precedence.
25
-
26
- html.light-theme,
27
- html.dark-theme {
28
- color: var(--text-color);
29
- }
1
+ // ─── Inherited-ink baseline ──────────────────────────────────────────────────
2
+
3
+ // Establishes the document's inherited `color`. Until this existed, NOTHING in
4
+ // the shell ever set `color` on `html` or `body` — `_theme-light.scss` and
5
+ // `_theme-dark.scss` declare custom properties only. So the inherited colour was
6
+ // the user-agent default, **black**, and every component that says
7
+ // `color: inherit` (fly-tree-nav rows, fly-breadcrumb's current crumb,
8
+ // fly-search-input's typed text, fly-card's meta slot, and any consumer markup
9
+ // that simply doesn't mention colour) rendered black ink on the smoked glass.
10
+
11
+ // That is why the "text is invisible in light theme" bug kept coming back app
12
+ // after app: it was never really an app's mistake. There was no baseline to
13
+ // inherit, so *forgetting* to set a colour was the failure mode, and the fix
14
+ // kept being applied one component at a time. `--text-color` is white in BOTH
15
+ // themes since the vibrancy flip, so one declaration settles it everywhere.
16
+
17
+ // Scoped to the two theme classes on purpose — NOT `:root`. A business app
18
+ // running STANDALONE renders on its own opaque paper (`--bg`, near-black
19
+ // `--ink`) and does not load `fly-theme` at all; keying on the class means a
20
+ // surface that never opted into a shell theme is never repainted white.
21
+
22
+ // A deliberate paper surface inside the shell (the documents editor) still
23
+ // brings its own dark ink locally — a component rule beats this baseline, which
24
+ // is exactly the intended precedence.
25
+
26
+ html.light-theme,
27
+ html.dark-theme {
28
+ color: var(--text-color);
29
+ }