@loworbitstudio/visor-theme-engine 0.23.0 → 0.25.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.
@@ -1,4 +1,4 @@
1
- import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-Wy5n0W9F.js';
1
+ import { k as GeneratedPrimitives, s as SemanticTokens, R as ResolvedThemeConfig } from '../types-ZPTjTL_-.js';
2
2
 
3
3
  /**
4
4
  * Adapter types for the Visor theme engine.
@@ -25,7 +25,7 @@ import {
25
25
  resolveThemeBrand,
26
26
  resolveThemeFonts,
27
27
  sectionComment
28
- } from "../chunk-BQU3VFCD.js";
28
+ } from "../chunk-C2DUPZVY.js";
29
29
 
30
30
  // src/adapters/brand-passthrough.ts
31
31
  var SENTINEL_COLOR = "#ff00ff";
@@ -1271,6 +1271,8 @@ var FIELD = "components/ui/field/field.module.css";
1271
1271
  var TEXT = "components/ui/text/text.module.css";
1272
1272
  var INLINE_EDIT = "components/ui/inline-edit/inline-edit.module.css";
1273
1273
  var SAVE_STATUS = "components/ui/save-status/save-status.module.css";
1274
+ var TOOLTIP = "components/ui/tooltip/tooltip.module.css";
1275
+ var ACTION_ROW = "components/ui/action-row/action-row.module.css";
1274
1276
  function at(file, fallback) {
1275
1277
  return [{ file, fallback }];
1276
1278
  }
@@ -1300,7 +1302,7 @@ var tableFamily = {
1300
1302
  { key: "cell-font-size", property: "font-size", description: "Body cell type size.", consumers: at(TABLE, "inherit") },
1301
1303
  { key: "cell-color", property: "color", description: "Body cell text colour.", consumers: at(TABLE, "var(--text-primary, #111827)") },
1302
1304
  { key: "cell-bg", property: "background-color", description: "Body cell fill. Sits above the shared `--dt-row-bg` surface role.", consumers: at(TABLE, "var(--dt-row-bg, transparent)") },
1303
- { key: "cell-border-top", property: "border-top", description: "Cell hairline.", consumers: at(TABLE, "1px solid var(--hairline, transparent)") },
1305
+ { key: "cell-border-top", property: "border-top", description: "Cell hairline.", consumers: at(TABLE, "var(--hairline-width, 1px) solid var(--hairline, transparent)") },
1304
1306
  { key: "footer-bg", property: "background-color", description: "Footer row fill.", consumers: at(TABLE, "var(--surface-muted, #f3f4f6)") },
1305
1307
  { key: "caption-color", property: "color", description: "Caption text colour.", consumers: at(TABLE, "var(--text-secondary, #6b7280)") }
1306
1308
  ]
@@ -1535,6 +1537,7 @@ var emptyStateFamily = {
1535
1537
  { key: "heading-color", property: "color", description: "Heading colour.", consumers: at(EMPTY_STATE, "var(--text-primary, #111827)") },
1536
1538
  { key: "description-font-size", property: "font-size", description: "Description type size.", consumers: at(EMPTY_STATE, "var(--font-size-sm, 0.875rem)") },
1537
1539
  { key: "description-color", property: "color", description: "Description colour.", consumers: at(EMPTY_STATE, "var(--text-secondary, #6b7280)") },
1540
+ { key: "error-color", property: "color", description: 'Icon, chip glyph and heading colour for `intent="error"`.', consumers: at(EMPTY_STATE, "var(--destructive, #dc2626)") },
1538
1541
  { key: "actions-gap", property: "gap", description: "Gap between placard actions.", consumers: at(EMPTY_STATE, "var(--spacing-2, 0.5rem)") }
1539
1542
  ]
1540
1543
  };
@@ -1606,7 +1609,7 @@ var tabsFamily = {
1606
1609
  { key: "list-padding", property: "padding", description: "Tab-rail inset.", consumers: at(TABS, "calc(var(--spacing-1, 0.25rem) * 0.75)") },
1607
1610
  { key: "list-color", property: "color", description: "Tab-rail base text colour.", consumers: at(TABS, "var(--text-secondary, #6b7280)") },
1608
1611
  { key: "list-bg", property: "background-color", description: "Segmented-variant rail fill.", consumers: at(TABS, "var(--surface-muted, #f3f4f6)") },
1609
- { key: "line-border-bottom", property: "border-bottom", description: "Underlined-variant rail rule.", consumers: at(TABS, "1px solid var(--hairline, var(--border-default, #e5e7eb))") },
1612
+ { key: "line-border-bottom", property: "border-bottom", description: "Underlined-variant rail rule (width, style and colour). Unbound, the rail rule is painted at `--hairline-width`; binding this draws your border instead.", consumers: at(TABS, "1px solid transparent") },
1610
1613
  { key: "trigger-radius", property: "border-radius", description: "Trigger corner rounding.", consumers: at(TABS, "var(--radius-md, 0.375rem)") },
1611
1614
  { key: "trigger-padding", property: "padding", description: "Trigger inset.", consumers: at(TABS, "var(--spacing-1, 0.25rem) var(--spacing-2, 0.5rem)") },
1612
1615
  { key: "trigger-font-size", property: "font-size", description: "Trigger type size.", consumers: at(TABS, "var(--font-size-sm, 0.875rem)") },
@@ -1898,14 +1901,85 @@ var switchFamily = {
1898
1901
  var saveStatusFamily = {
1899
1902
  family: "save-status",
1900
1903
  prefix: "save-status",
1901
- description: "SaveStatus readout \u2014 the fixed-width slot and its type, so the readout never reflows its row.",
1904
+ description: "SaveStatus readout \u2014 the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row.",
1902
1905
  tokens: [
1903
- { key: "width", property: "width", description: "Slot width. Fixed, so the row never reflows between states.", consumers: at(SAVE_STATUS, "7.5rem") },
1906
+ { key: "width", property: "width", description: "Slot width. Unbound, the slot is as wide as the longest label in the current font plus the retry mark, the same in every state. Bind it to pin a width; a longer label then truncates inside it.", consumers: at(SAVE_STATUS, "auto") },
1907
+ { key: "syncing-color", property: "color", description: "Ink of the Syncing state (a first sync from an outside source). Unbound it reads `--text-secondary`.", consumers: at(SAVE_STATUS, "var(--text-secondary, #6b7280)") },
1908
+ { key: "dot-size", property: "width", description: 'Diameter of the dot that draws Unsaved when `unsavedAs="dot"`. The slot width does not change.', consumers: at(SAVE_STATUS, "var(--spacing-2, 0.5rem)") },
1904
1909
  { key: "font-family", property: "font-family", description: "Readout family (e.g. the theme's mono).", consumers: at(SAVE_STATUS, "revert-layer") },
1905
1910
  { key: "text-transform", property: "text-transform", description: "Readout casing.", consumers: at(SAVE_STATUS, "revert-layer") },
1906
1911
  { key: "letter-spacing", property: "letter-spacing", description: "Readout tracking.", consumers: at(SAVE_STATUS, "revert-layer") }
1907
1912
  ]
1908
1913
  };
1914
+ var tooltipFamily = {
1915
+ family: "tooltip",
1916
+ prefix: "tooltip",
1917
+ description: "Tooltip ground and ink, so a dark theme can keep the label legible without retuning the shared inverse pair other components read.",
1918
+ tokens: [
1919
+ { key: "bg", property: "background-color", description: "Tooltip ground.", consumers: at(TOOLTIP, "var(--surface-overlay, #111827)") },
1920
+ { key: "text", property: "color", description: "Tooltip label colour.", consumers: at(TOOLTIP, "var(--text-inverse, #ffffff)") }
1921
+ ]
1922
+ };
1923
+ var actionRowFamily = {
1924
+ family: "action-row",
1925
+ prefix: "action-row",
1926
+ description: "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token.",
1927
+ tokens: [
1928
+ { key: "min-height", property: "min-height", description: "Row floor height; the whole row is the target.", consumers: at(ACTION_ROW, "2.75rem") },
1929
+ { key: "padding", property: "padding", description: "Row inset, kept inside the hover and selected fills.", consumers: at(ACTION_ROW, "var(--spacing-2, 0.5rem) var(--spacing-3, 0.75rem)") },
1930
+ { key: "gap", property: "gap", description: "Gap between the leading slot, the title block and the trailing slot.", consumers: at(ACTION_ROW, "var(--spacing-3, 0.75rem)") },
1931
+ { key: "radius", property: "border-radius", description: "Row corner rounding (the hover and selected fill).", consumers: at(ACTION_ROW, "var(--radius-md, 0.375rem)") },
1932
+ { key: "hover-bg", property: "background-color", description: "Hover fill.", consumers: at(ACTION_ROW, "var(--surface-interactive-hover, #f3f4f6)") },
1933
+ { key: "open-bg", property: "background-color", description: "Fill of a row that has opened a picker or popover (`aria-expanded`).", consumers: at(ACTION_ROW, "var(--surface-interactive-hover, #f3f4f6)") },
1934
+ { key: "selected-bg", property: "background-color", description: "Selected fill. A lifted fill, never white.", consumers: at(ACTION_ROW, "var(--surface-interactive-active, #e5e7eb)") },
1935
+ { key: "selected-edge-color", property: "outline-color", description: "Selected row's inset edge colour. Its width is `--control-state-edge-width`, so `edges: off` keeps it.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") },
1936
+ { key: "disabled-opacity", property: "opacity", description: "Disabled row opacity.", consumers: at(ACTION_ROW, "var(--opacity-50, 0.5)") },
1937
+ { key: "title-weight", property: "font-weight", description: "Title weight.", consumers: at(ACTION_ROW, "var(--font-weight-medium, 500)") },
1938
+ { key: "line-color", property: "color", description: "Second-line colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") },
1939
+ { key: "leading-color", property: "color", description: "Leading icon colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") },
1940
+ { key: "leading-size", property: "font-size", description: "Leading icon size.", consumers: at(ACTION_ROW, "1.25rem") },
1941
+ { key: "trailing-color", property: "color", description: "Trailing value or caret colour.", consumers: at(ACTION_ROW, "var(--text-secondary, #6b7280)") }
1942
+ ]
1943
+ };
1944
+ var HAIRLINE_PX = [
1945
+ "components/ui/bulk-action-bar/bulk-action-bar.module.css",
1946
+ "components/ui/dialog/dialog.module.css",
1947
+ "components/ui/dropdown-menu/dropdown-menu.module.css",
1948
+ "components/ui/matrix-table/matrix-table.module.css",
1949
+ TABLE,
1950
+ TABS
1951
+ ];
1952
+ var HAIRLINE_THIN = [
1953
+ ACTION_ROW,
1954
+ "blocks/dialog-form/dialog-form.module.css",
1955
+ "components/ui/coherence-check/coherence-check.module.css",
1956
+ "components/ui/doc-frame/doc-frame.module.css",
1957
+ "components/ui/doc-nav/doc-nav.module.css",
1958
+ "components/ui/editable-block/editable-block.module.css",
1959
+ "components/ui/fidelity-mirror/fidelity-mirror.module.css",
1960
+ "components/ui/infographic-bar/infographic-bar.module.css",
1961
+ "components/ui/popover/popover.module.css",
1962
+ "components/ui/section-nav/section-nav.module.css",
1963
+ "components/ui/specimen-card/specimen-card.module.css",
1964
+ "components/ui/structured-prompt/structured-prompt.module.css",
1965
+ "components/visual/browser-frame/browser-frame.module.css"
1966
+ ];
1967
+ var hairlineFamily = {
1968
+ family: "hairline",
1969
+ prefix: "hairline",
1970
+ description: "The one switch for hairlines (VI-680). Every hairline rule, divider and ring reads its weight from `--hairline-width`, so `width: 0` removes them all at once. Independent of the `control` edge switch.",
1971
+ tokens: [
1972
+ {
1973
+ key: "width",
1974
+ property: "border-width / box-shadow spread / height",
1975
+ description: "THE switch. Hairline weight for every `--hairline` / `--hairline-strong` rule; `0` turns every hairline off. Focus and invalid edges are untouched.",
1976
+ consumers: [
1977
+ ...each(HAIRLINE_PX, "1px"),
1978
+ ...each(HAIRLINE_THIN, "var(--stroke-width-thin, 1px)")
1979
+ ]
1980
+ }
1981
+ ]
1982
+ };
1909
1983
  var adminUiFamily = {
1910
1984
  family: "admin-ui",
1911
1985
  prefix: "admin-ui",
@@ -1935,11 +2009,14 @@ var COMPONENT_TOKEN_FAMILIES = [
1935
2009
  spinnerFamily,
1936
2010
  checkboxFamily,
1937
2011
  controlFamily,
2012
+ hairlineFamily,
1938
2013
  fieldFamily,
1939
2014
  buttonFamily,
1940
2015
  textFamily,
1941
2016
  switchFamily,
1942
2017
  saveStatusFamily,
2018
+ tooltipFamily,
2019
+ actionRowFamily,
1943
2020
  adminUiFamily
1944
2021
  ];
1945
2022
  var COMPONENT_TOKEN_FAMILY_BY_NAME = new Map(
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, C as ColorScheme, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale, p as ColorRole, q as SelectiveShadeScale, r as RGB, P as ParsedColor, O as OKLCH, s as SemanticTokens, t as ShadeStep } from './types-Wy5n0W9F.js';
2
- export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-Wy5n0W9F.js';
1
+ import { F as FontResolveOptions, a as FontResolution, V as VisorTypography, b as FontDisplayStrategy, T as ThemeFontResult, G as GoogleFontEntry, c as VisorBrand, B as BrandSlot, d as BrandSource, e as BrandResolution, C as ColorScheme, f as ThemeBrandResult, g as BrandStrategy, h as BrandStrategyContext, i as BrandStrategyIssue, j as BrandStrategyValidationResult, S as SerializedBrandStrategy, R as ResolvedThemeConfig, k as GeneratedPrimitives, l as ThemeOutput, m as ThemeData, n as VisorThemeConfig, o as FullShadeScale, p as ColorRole, q as SelectiveShadeScale, r as RGB, P as ParsedColor, O as OKLCH, s as SemanticTokens, t as ShadeStep } from './types-ZPTjTL_-.js';
2
+ export { u as BRAND_VARIANTS, v as BRAND_VISIBILITIES, w as BrandAccessibility, x as BrandArchetype, y as BrandBoilerplate, z as BrandColorPairing, A as BrandColorUsage, D as BrandContrastTarget, E as BrandGoverns, H as BrandLexiconEntry, I as BrandMessaging, J as BrandPersonalityTrait, K as BrandPillar, L as BrandPositioning, M as BrandStrategyIssueSeverity, N as BrandToneEntry, Q as BrandVariant, U as BrandVisibility, W as BrandVoice, X as BrandVoiceTrait, Y as COMPONENT_TOKEN_FAMILIES, Z as COMPONENT_TOKEN_FAMILY_BY_NAME, _ as ColorFormat, $ as ComponentTokenBinding, a0 as ComponentTokenBindings, a1 as ComponentTokenConsumer, a2 as ComponentTokenFamily, a3 as ComponentTokenSpec, a4 as DEFAULT_BRAND_STRATEGY_SURFACES, a5 as DEFAULT_BRAND_STRATEGY_TONE_STATES, a6 as FontSource, a7 as GOVERNS_WILDCARD, a8 as RGBA, a9 as ResolvedComponentTokens, aa as SemanticTokenValue, ab as allComponentTokenNames, ac as componentTokenName, ad as hasComponentBindings, ae as resolveComponentBindings, af as validateComponentBindings } from './types-ZPTjTL_-.js';
3
3
 
4
4
  /**
5
5
  * Font resolver — maps font family names to loadable font resources.
@@ -408,6 +408,14 @@ var properties = {
408
408
  ],
409
409
  description: "The one switch for form-control edges (VI-655). 'off' removes the resting edge of every form control, dashed drop-target and empty-state edges included, with no layout shift; focus and invalid stay visible. Shorthand for components.control.edge-width: \"0\" (an explicit binding there wins). Defaults to 'on'. Optional."
410
410
  },
411
+ hairlines: {
412
+ type: "string",
413
+ "enum": [
414
+ "on",
415
+ "off"
416
+ ],
417
+ description: "The one switch for hairlines (VI-680). 'off' sets --hairline-width to 0, so every hairline rule, divider and ring disappears. Shorthand for components.hairline.width: \"0\" (an explicit binding there wins). Independent of 'edges'. Defaults to 'on'. Optional."
418
+ },
411
419
  colors: {
412
420
  type: "object",
413
421
  description: "Color definitions for light mode. Only primary is required — all others have sensible defaults.",
@@ -1089,7 +1097,7 @@ var properties = {
1089
1097
  },
1090
1098
  "empty-state": {
1091
1099
  type: "object",
1092
- description: "Empty-state placard — surface, icon chip and copy scale. Keys: radius, text-color, gap, padding, bg, border, icon-size, icon-radius, icon-bg, icon-color, heading-font-family, heading-font-size, heading-font-weight, heading-color, description-font-size, description-color, actions-gap.",
1100
+ description: "Empty-state placard — surface, icon chip and copy scale. Keys: radius, text-color, gap, padding, bg, border, icon-size, icon-radius, icon-bg, icon-color, heading-font-family, heading-font-size, heading-font-weight, heading-color, description-font-size, description-color, error-color, actions-gap.",
1093
1101
  additionalProperties: {
1094
1102
  $ref: "#/$defs/componentTokenBinding"
1095
1103
  }
@@ -1136,6 +1144,13 @@ var properties = {
1136
1144
  $ref: "#/$defs/componentTokenBinding"
1137
1145
  }
1138
1146
  },
1147
+ hairline: {
1148
+ type: "object",
1149
+ description: "The one switch for hairlines (VI-680). Every hairline rule, divider and ring reads its weight from `--hairline-width`, so `width: 0` removes them all at once. Independent of the `control` edge switch. Keys: width.",
1150
+ additionalProperties: {
1151
+ $ref: "#/$defs/componentTokenBinding"
1152
+ }
1153
+ },
1139
1154
  control: {
1140
1155
  type: "object",
1141
1156
  description: "The one switch for form-control edges (VI-655). Every form control draws its resting edge from these, so `edge-width: 0` removes every resting edge at once, dashed edges included, with no layout shift. Keys: edge-width, edge-color, edge-style, drop-edge-width, drop-edge-style, state-edge-width.",
@@ -1180,7 +1195,21 @@ var properties = {
1180
1195
  },
1181
1196
  "save-status": {
1182
1197
  type: "object",
1183
- description: "SaveStatus readout — the fixed-width slot and its type, so the readout never reflows its row. Keys: width, font-family, text-transform, letter-spacing.",
1198
+ description: "SaveStatus readout — the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, dot-size, font-family, text-transform, letter-spacing.",
1199
+ additionalProperties: {
1200
+ $ref: "#/$defs/componentTokenBinding"
1201
+ }
1202
+ },
1203
+ tooltip: {
1204
+ type: "object",
1205
+ description: "Tooltip ground and ink, so a dark theme can keep the label legible without retuning the shared inverse pair other components read. Keys: bg, text.",
1206
+ additionalProperties: {
1207
+ $ref: "#/$defs/componentTokenBinding"
1208
+ }
1209
+ },
1210
+ "action-row": {
1211
+ type: "object",
1212
+ description: "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token. Keys: min-height, padding, gap, radius, hover-bg, open-bg, selected-bg, selected-edge-color, disabled-opacity, title-weight, line-color, leading-color, leading-size, trailing-color.",
1184
1213
  additionalProperties: {
1185
1214
  $ref: "#/$defs/componentTokenBinding"
1186
1215
  }
package/dist/index.js CHANGED
@@ -63,7 +63,7 @@ import {
63
63
  rgbToOklch,
64
64
  serializeColor,
65
65
  validateComponentBindings
66
- } from "./chunk-BQU3VFCD.js";
66
+ } from "./chunk-C2DUPZVY.js";
67
67
 
68
68
  // src/fonts/validate-coverage.ts
69
69
  var FONT_VAR_RE = /--font-(heading|display|body|sans|mono)\s*:\s*([^;]+);/g;
@@ -655,6 +655,11 @@ var visor_theme_schema_default = {
655
655
  enum: ["on", "off"],
656
656
  description: `The one switch for form-control edges (VI-655). 'off' removes the resting edge of every form control, dashed drop-target and empty-state edges included, with no layout shift; focus and invalid stay visible. Shorthand for components.control.edge-width: "0" (an explicit binding there wins). Defaults to 'on'. Optional.`
657
657
  },
658
+ hairlines: {
659
+ type: "string",
660
+ enum: ["on", "off"],
661
+ description: `The one switch for hairlines (VI-680). 'off' sets --hairline-width to 0, so every hairline rule, divider and ring disappears. Shorthand for components.hairline.width: "0" (an explicit binding there wins). Independent of 'edges'. Defaults to 'on'. Optional.`
662
+ },
658
663
  colors: {
659
664
  type: "object",
660
665
  description: "Color definitions for light mode. Only primary is required \u2014 all others have sensible defaults.",
@@ -1128,7 +1133,7 @@ var visor_theme_schema_default = {
1128
1133
  },
1129
1134
  "empty-state": {
1130
1135
  type: "object",
1131
- description: "Empty-state placard \u2014 surface, icon chip and copy scale. Keys: radius, text-color, gap, padding, bg, border, icon-size, icon-radius, icon-bg, icon-color, heading-font-family, heading-font-size, heading-font-weight, heading-color, description-font-size, description-color, actions-gap.",
1136
+ description: "Empty-state placard \u2014 surface, icon chip and copy scale. Keys: radius, text-color, gap, padding, bg, border, icon-size, icon-radius, icon-bg, icon-color, heading-font-family, heading-font-size, heading-font-weight, heading-color, description-font-size, description-color, error-color, actions-gap.",
1132
1137
  additionalProperties: {
1133
1138
  $ref: "#/$defs/componentTokenBinding"
1134
1139
  }
@@ -1175,6 +1180,13 @@ var visor_theme_schema_default = {
1175
1180
  $ref: "#/$defs/componentTokenBinding"
1176
1181
  }
1177
1182
  },
1183
+ hairline: {
1184
+ type: "object",
1185
+ description: "The one switch for hairlines (VI-680). Every hairline rule, divider and ring reads its weight from `--hairline-width`, so `width: 0` removes them all at once. Independent of the `control` edge switch. Keys: width.",
1186
+ additionalProperties: {
1187
+ $ref: "#/$defs/componentTokenBinding"
1188
+ }
1189
+ },
1178
1190
  control: {
1179
1191
  type: "object",
1180
1192
  description: "The one switch for form-control edges (VI-655). Every form control draws its resting edge from these, so `edge-width: 0` removes every resting edge at once, dashed edges included, with no layout shift. Keys: edge-width, edge-color, edge-style, drop-edge-width, drop-edge-style, state-edge-width.",
@@ -1219,7 +1231,21 @@ var visor_theme_schema_default = {
1219
1231
  },
1220
1232
  "save-status": {
1221
1233
  type: "object",
1222
- description: "SaveStatus readout \u2014 the fixed-width slot and its type, so the readout never reflows its row. Keys: width, font-family, text-transform, letter-spacing.",
1234
+ description: "SaveStatus readout \u2014 the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, dot-size, font-family, text-transform, letter-spacing.",
1235
+ additionalProperties: {
1236
+ $ref: "#/$defs/componentTokenBinding"
1237
+ }
1238
+ },
1239
+ tooltip: {
1240
+ type: "object",
1241
+ description: "Tooltip ground and ink, so a dark theme can keep the label legible without retuning the shared inverse pair other components read. Keys: bg, text.",
1242
+ additionalProperties: {
1243
+ $ref: "#/$defs/componentTokenBinding"
1244
+ }
1245
+ },
1246
+ "action-row": {
1247
+ type: "object",
1248
+ description: "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token. Keys: min-height, padding, gap, radius, hover-bg, open-bg, selected-bg, selected-edge-color, disabled-opacity, title-weight, line-color, leading-color, leading-size, trailing-color.",
1223
1249
  additionalProperties: {
1224
1250
  $ref: "#/$defs/componentTokenBinding"
1225
1251
  }
@@ -1509,7 +1535,8 @@ var KNOWN_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
1509
1535
  "motion",
1510
1536
  "overrides",
1511
1537
  "components",
1512
- "edges"
1538
+ "edges",
1539
+ "hairlines"
1513
1540
  ]);
1514
1541
  var KNOWN_COLOR_KEYS = /* @__PURE__ */ new Set([
1515
1542
  "primary",
@@ -1791,6 +1818,9 @@ function validateConfig(config) {
1791
1818
  if (obj.edges !== void 0 && obj.edges !== "on" && obj.edges !== "off") {
1792
1819
  errors.push("'edges' must be either 'on' or 'off'");
1793
1820
  }
1821
+ if (obj.hairlines !== void 0 && obj.hairlines !== "on" && obj.hairlines !== "off") {
1822
+ errors.push("'hairlines' must be either 'on' or 'off'");
1823
+ }
1794
1824
  if (typeof obj.colors !== "object" || obj.colors === null) {
1795
1825
  errors.push("'colors' is required and must be an object");
1796
1826
  return { valid: false, errors };
@@ -2173,9 +2203,16 @@ function resolveConfig(config) {
2173
2203
  // component-tokens.ts owns their shape, there is nothing to default.
2174
2204
  // VI-655: `edges: off` folds in here as `control.edge-width: 0`, so every
2175
2205
  // adapter emits the switch through the same component-token path.
2176
- components: config.edges === "off" ? {
2206
+ // VI-680: `hairlines: off` folds in the same way, as `hairline.width: 0`.
2207
+ // The two switches are independent: each touches only its own family.
2208
+ components: config.edges === "off" || config.hairlines === "off" ? {
2177
2209
  ...config.components,
2178
- control: { "edge-width": "0", ...config.components?.control }
2210
+ ...config.edges === "off" && {
2211
+ control: { "edge-width": "0", ...config.components?.control }
2212
+ },
2213
+ ...config.hairlines === "off" && {
2214
+ hairline: { width: "0", ...config.components?.hairline }
2215
+ }
2179
2216
  } : config.components,
2180
2217
  originalColors,
2181
2218
  colorFormats
@@ -838,6 +838,14 @@ interface VisorThemeConfig {
838
838
  * `components.control.edge-width` wins over it. Default: `on`.
839
839
  */
840
840
  edges?: "on" | "off";
841
+ /**
842
+ * VI-680: the one switch for hairlines. `off` sets `--hairline-width` to 0 so
843
+ * every hairline rule, divider and ring (the `--hairline` / `--hairline-strong`
844
+ * family) disappears. Shorthand for `components.hairline.width: "0"`, and an
845
+ * explicit `components.hairline.width` wins over it. Independent of `edges`:
846
+ * neither switch touches the other's tokens. Default: `on`.
847
+ */
848
+ hairlines?: "on" | "off";
841
849
  /**
842
850
  * Migration metadata — optional, additive, consumed by `visor migrate` commands.
843
851
  * Does not affect CSS generation or theme application.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@loworbitstudio/visor-theme-engine",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Theme engine for the Visor design system — shade generation, token mapping, font resolution, and import/export for .visor.yaml themes.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -39,6 +39,11 @@
39
39
  "enum": ["on", "off"],
40
40
  "description": "The one switch for form-control edges (VI-655). 'off' removes the resting edge of every form control, dashed drop-target and empty-state edges included, with no layout shift; focus and invalid stay visible. Shorthand for components.control.edge-width: \"0\" (an explicit binding there wins). Defaults to 'on'. Optional."
41
41
  },
42
+ "hairlines": {
43
+ "type": "string",
44
+ "enum": ["on", "off"],
45
+ "description": "The one switch for hairlines (VI-680). 'off' sets --hairline-width to 0, so every hairline rule, divider and ring disappears. Shorthand for components.hairline.width: \"0\" (an explicit binding there wins). Independent of 'edges'. Defaults to 'on'. Optional."
46
+ },
42
47
  "colors": {
43
48
  "type": "object",
44
49
  "description": "Color definitions for light mode. Only primary is required — all others have sensible defaults.",
@@ -512,7 +517,7 @@
512
517
  },
513
518
  "empty-state": {
514
519
  "type": "object",
515
- "description": "Empty-state placard — surface, icon chip and copy scale. Keys: radius, text-color, gap, padding, bg, border, icon-size, icon-radius, icon-bg, icon-color, heading-font-family, heading-font-size, heading-font-weight, heading-color, description-font-size, description-color, actions-gap.",
520
+ "description": "Empty-state placard — surface, icon chip and copy scale. Keys: radius, text-color, gap, padding, bg, border, icon-size, icon-radius, icon-bg, icon-color, heading-font-family, heading-font-size, heading-font-weight, heading-color, description-font-size, description-color, error-color, actions-gap.",
516
521
  "additionalProperties": {
517
522
  "$ref": "#/$defs/componentTokenBinding"
518
523
  }
@@ -559,6 +564,13 @@
559
564
  "$ref": "#/$defs/componentTokenBinding"
560
565
  }
561
566
  },
567
+ "hairline": {
568
+ "type": "object",
569
+ "description": "The one switch for hairlines (VI-680). Every hairline rule, divider and ring reads its weight from `--hairline-width`, so `width: 0` removes them all at once. Independent of the `control` edge switch. Keys: width.",
570
+ "additionalProperties": {
571
+ "$ref": "#/$defs/componentTokenBinding"
572
+ }
573
+ },
562
574
  "control": {
563
575
  "type": "object",
564
576
  "description": "The one switch for form-control edges (VI-655). Every form control draws its resting edge from these, so `edge-width: 0` removes every resting edge at once, dashed edges included, with no layout shift. Keys: edge-width, edge-color, edge-style, drop-edge-width, drop-edge-style, state-edge-width.",
@@ -603,7 +615,21 @@
603
615
  },
604
616
  "save-status": {
605
617
  "type": "object",
606
- "description": "SaveStatus readout — the fixed-width slot and its type, so the readout never reflows its row. Keys: width, font-family, text-transform, letter-spacing.",
618
+ "description": "SaveStatus readout — the fixed-width slot, its type, the Syncing ink and the Unsaved dot, so the readout never reflows its row. Keys: width, syncing-color, dot-size, font-family, text-transform, letter-spacing.",
619
+ "additionalProperties": {
620
+ "$ref": "#/$defs/componentTokenBinding"
621
+ }
622
+ },
623
+ "tooltip": {
624
+ "type": "object",
625
+ "description": "Tooltip ground and ink, so a dark theme can keep the label legible without retuning the shared inverse pair other components read. Keys: bg, text.",
626
+ "additionalProperties": {
627
+ "$ref": "#/$defs/componentTokenBinding"
628
+ }
629
+ },
630
+ "action-row": {
631
+ "type": "object",
632
+ "description": "ActionRow state fills, edge and geometry: rest, hover, open, selected and disabled each read their own token. Keys: min-height, padding, gap, radius, hover-bg, open-bg, selected-bg, selected-edge-color, disabled-opacity, title-weight, line-color, leading-color, leading-size, trailing-color.",
607
633
  "additionalProperties": {
608
634
  "$ref": "#/$defs/componentTokenBinding"
609
635
  }