@colixsystems/widget-sdk 0.140.0 → 0.141.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/README.md CHANGED
@@ -70,7 +70,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
70
70
 
71
71
  ## Status
72
72
 
73
- `v0.140.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
73
+ `v0.141.0` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
74
+
75
+ ### What's new in 0.141.0 (contract 1.109.0)
76
+
77
+ **The app's global navigation is the sidebar alone — `top-bar` and `bottom-tabs` leave `CONTRACT.themeMenuTypes` (sc-7352, REQ-NAV-STRUCTURE).** A top bar is now a PAGE's local bar drawing a saved menu (REQ-NAV-LOCAL, sc-7103): the same tab row the `top-bar` shape used to draw, themed by the same `topBar` tokens (`resolveTopBarTokens` is unchanged), worn by one page under the sidebar. Two "top bars" competing for one word and one token set was the reason.
78
+
79
+ - `normaliseNavigation` resolves a stored `top-bar` or `bottom-tabs` to `sidebar`, so a host never has to guard the value. `menuItemCap` / `quickBarCap` keep their signatures (`quickBarCap("sidebar")` is still 5).
80
+ - The global menu never lived in the shape (it is the pages' `show_in_menu` flags), so a workspace that stored a retired shape already draws the sidebar with the same menu. The sc-7352 backfill deletes the stored key BEFORE the shapes disappear; a bottom-tabs app's phone strip becomes the sidebar's quick bar.
81
+ - Host-integration surface only; no author-facing hook, prop or manifest field changed. `CONTRACT.version` → `1.109.0`.
74
82
 
75
83
  ### What's new in 0.140.0 (contract 1.108.0)
76
84
 
package/dist/contract.cjs CHANGED
@@ -217,36 +217,20 @@ const THEME_WIDGET_STYLES = Object.freeze({
217
217
  // PlayerChrome and the compiler's navigator -- so a type cannot be offered to an
218
218
  // author without every host actually drawing it.
219
219
  //
220
- // `maxItems` caps how many menu pages the chrome draws at once, and its meaning
221
- // differs per type deliberately. The sidebar's mobile quick bar is a SECONDARY
222
- // curated bar, so its own cap may drop a page -- the rail still lists every one.
223
- // A bottom-tabs strip IS the menu, so its cap must never drop one: the surplus
224
- // moves behind a "More" sheet instead.
220
+ // REQ-NAV-STRUCTURE (sc-7352): the app's GLOBAL navigation has exactly one
221
+ // shape, the sidebar. A top bar is a PAGE's local bar drawing a saved menu
222
+ // (REQ-NAV-LOCAL), themed by the `topBar` tokens, never an app-wide menu shape.
223
+ // `maxItems` is the menu cap (none for a rail); `quickBarMaxItems` caps the
224
+ // SECONDARY mobile quick bar, which may drop a page because the rail and the
225
+ // drawer still list every one.
225
226
  const THEME_MENU_TYPES = Object.freeze({
226
227
  sidebar: Object.freeze({
227
228
  name: "Sidebar",
228
229
  summary:
229
230
  "A persistent left rail on desktop; a hamburger drawer plus the optional bottom quick bar on mobile.",
230
231
  maxItems: null,
231
- // The SECONDARY mobile quick bar's cap. It may drop a page past it, because
232
- // the rail and the drawer still list every one -- the opposite of
233
- // `bottom-tabs`' `maxItems`, where the strip IS the menu.
234
232
  quickBarMaxItems: 5,
235
233
  }),
236
- "top-bar": Object.freeze({
237
- name: "Top bar",
238
- summary:
239
- "A tab row beneath the app header at every width -- icon and label per page, scrolling sideways when it runs out of room. No rail at any width.",
240
- maxItems: null,
241
- quickBarMaxItems: null,
242
- }),
243
- "bottom-tabs": Object.freeze({
244
- name: "Bottom tabs",
245
- summary:
246
- "A sticky bottom strip at every width -- the phone-native shape. Pages past the cap move behind a More sheet.",
247
- maxItems: 4,
248
- quickBarMaxItems: null,
249
- }),
250
234
  });
251
235
  const THEME_SPACING_SCALE = Object.freeze({
252
236
  min: 0.5,
@@ -3976,7 +3960,17 @@ const CONTRACT = deepFreeze({
3976
3960
  // a stored hex is untouched and still renders byte-identically, and an
3977
3961
  // unresolvable binding falls back to the field's `themeDefault` rather
3978
3962
  // than painting blank.
3979
- version: "1.108.0",
3963
+ // 1.109.0: BREAKING on the pre-1.0 channel (sc-7352) — `themeMenuTypes`
3964
+ // loses `top-bar` and `bottom-tabs`: the app's global navigation is the
3965
+ // sidebar alone, and a top bar is a page's LOCAL bar drawing a saved menu
3966
+ // (REQ-NAV-LOCAL), themed by the same `topBar` tokens. A stored
3967
+ // `navigation.menuType` of a retired shape resolves to `sidebar` through
3968
+ // `normaliseNavigation`, so no host has to guard the value; the global
3969
+ // menu never lived in the shape, so such apps already draw the sidebar,
3970
+ // and the sc-7352 backfill deletes the stored key (a bottom-tabs app's
3971
+ // strip becomes the sidebar's quick bar) BEFORE this ships.
3972
+ // `menuItemCap` / `quickBarCap` keep their signatures.
3973
+ version: "1.109.0",
3980
3974
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3981
3975
  hooks: HOOKS,
3982
3976
  primitives: PRIMITIVES,
package/dist/contract.js CHANGED
@@ -217,36 +217,20 @@ const THEME_WIDGET_STYLES = Object.freeze({
217
217
  // PlayerChrome and the compiler's navigator -- so a type cannot be offered to an
218
218
  // author without every host actually drawing it.
219
219
  //
220
- // `maxItems` caps how many menu pages the chrome draws at once, and its meaning
221
- // differs per type deliberately. The sidebar's mobile quick bar is a SECONDARY
222
- // curated bar, so its own cap may drop a page -- the rail still lists every one.
223
- // A bottom-tabs strip IS the menu, so its cap must never drop one: the surplus
224
- // moves behind a "More" sheet instead.
220
+ // REQ-NAV-STRUCTURE (sc-7352): the app's GLOBAL navigation has exactly one
221
+ // shape, the sidebar. A top bar is a PAGE's local bar drawing a saved menu
222
+ // (REQ-NAV-LOCAL), themed by the `topBar` tokens, never an app-wide menu shape.
223
+ // `maxItems` is the menu cap (none for a rail); `quickBarMaxItems` caps the
224
+ // SECONDARY mobile quick bar, which may drop a page because the rail and the
225
+ // drawer still list every one.
225
226
  const THEME_MENU_TYPES = Object.freeze({
226
227
  sidebar: Object.freeze({
227
228
  name: "Sidebar",
228
229
  summary:
229
230
  "A persistent left rail on desktop; a hamburger drawer plus the optional bottom quick bar on mobile.",
230
231
  maxItems: null,
231
- // The SECONDARY mobile quick bar's cap. It may drop a page past it, because
232
- // the rail and the drawer still list every one -- the opposite of
233
- // `bottom-tabs`' `maxItems`, where the strip IS the menu.
234
232
  quickBarMaxItems: 5,
235
233
  }),
236
- "top-bar": Object.freeze({
237
- name: "Top bar",
238
- summary:
239
- "A tab row beneath the app header at every width -- icon and label per page, scrolling sideways when it runs out of room. No rail at any width.",
240
- maxItems: null,
241
- quickBarMaxItems: null,
242
- }),
243
- "bottom-tabs": Object.freeze({
244
- name: "Bottom tabs",
245
- summary:
246
- "A sticky bottom strip at every width -- the phone-native shape. Pages past the cap move behind a More sheet.",
247
- maxItems: 4,
248
- quickBarMaxItems: null,
249
- }),
250
234
  });
251
235
  const THEME_SPACING_SCALE = Object.freeze({
252
236
  min: 0.5,
@@ -3976,7 +3960,17 @@ const CONTRACT = deepFreeze({
3976
3960
  // a stored hex is untouched and still renders byte-identically, and an
3977
3961
  // unresolvable binding falls back to the field's `themeDefault` rather
3978
3962
  // than painting blank.
3979
- version: "1.108.0",
3963
+ // 1.109.0: BREAKING on the pre-1.0 channel (sc-7352) — `themeMenuTypes`
3964
+ // loses `top-bar` and `bottom-tabs`: the app's global navigation is the
3965
+ // sidebar alone, and a top bar is a page's LOCAL bar drawing a saved menu
3966
+ // (REQ-NAV-LOCAL), themed by the same `topBar` tokens. A stored
3967
+ // `navigation.menuType` of a retired shape resolves to `sidebar` through
3968
+ // `normaliseNavigation`, so no host has to guard the value; the global
3969
+ // menu never lived in the shape, so such apps already draw the sidebar,
3970
+ // and the sc-7352 backfill deletes the stored key (a bottom-tabs app's
3971
+ // strip becomes the sidebar's quick bar) BEFORE this ships.
3972
+ // `menuItemCap` / `quickBarCap` keep their signatures.
3973
+ version: "1.109.0",
3980
3974
  sharedTranslationKeys: SHARED_TRANSLATION_KEYS,
3981
3975
  hooks: HOOKS,
3982
3976
  primitives: PRIMITIVES,
package/dist/host.d.ts CHANGED
@@ -150,10 +150,11 @@ export function createToastController(
150
150
  ): ToastController;
151
151
 
152
152
  // REQ-NAV-STRUCTURE — the navigation SHAPE a host draws its chrome in. The
153
- // vocabulary is closed by `CONTRACT.themeMenuTypes`; these are the resolvers
154
- // both hosts switch on.
153
+ // vocabulary is closed by `CONTRACT.themeMenuTypes`; since sc-7352 it holds
154
+ // the sidebar alone (a top bar is a page's local bar, REQ-NAV-LOCAL), and any
155
+ // stored value outside it resolves to `sidebar`.
155
156
 
156
- export type ThemeMenuType = "sidebar" | "top-bar" | "bottom-tabs";
157
+ export type ThemeMenuType = "sidebar";
157
158
 
158
159
  export interface ResolvedNavigation {
159
160
  menuType: ThemeMenuType;
@@ -168,8 +169,7 @@ export function normaliseNavigation(navigation: unknown): ResolvedNavigation;
168
169
 
169
170
  /**
170
171
  * How many menu pages this shape may draw at once, or `null` for no cap. The
171
- * cap's meaning differs per type: the sidebar's quick bar may drop a page past
172
- * it, a bottom-tabs strip must move its surplus behind a More sheet.
172
+ * sidebar has none — its rail and drawer list every menu page.
173
173
  */
174
174
  export function menuItemCap(menuType: string): number | null;
175
175
 
@@ -232,16 +232,14 @@ export interface TopBarTokens {
232
232
  * label plus its indicator. Falls back to the RAIL's `activeColor` and then
233
233
  * to the brand, so an app states its navigation colour once. */
234
234
  activeColor: string;
235
- /** How a `tabs` menu row marks its current page. `underline` keeps the tab in
236
- * the bar's surface behind an indicator; `attached` makes it a folder tab
237
- * that takes `contentSurface` and sits over the row's divider. Meaningless
238
- * under the `links` style, which draws no tabs. */
235
+ /** How a local bar marks its current page. `underline` keeps the tab in the
236
+ * bar's surface behind an indicator; `attached` cuts the bar away under the
237
+ * current tab so the page shows through it. */
239
238
  tabStyle: "underline" | "attached";
240
- /** The surface an `attached` tab AND its content panel share — always
241
- * opaque, because a translucent tab would show the bar through the page it
242
- * is part of. Honours `tabActiveBackgroundColor`, so moving the tab moves
243
- * the page with it. */
244
- contentSurface: string;
239
+ /** Under `attached`, the colour the PANEL behind the row paints — the
240
+ * authored `tabActiveBackgroundColor`, alpha kept — or null for the page's
241
+ * own wash. The current tab paints nothing itself. */
242
+ contentSurface: string | null;
245
243
  /** A tab's own surface, authored or null. Null paints none and the bar shows
246
244
  * through. */
247
245
  tabBackgroundColor: string | null;
package/dist/host.js CHANGED
@@ -52,15 +52,14 @@ export {
52
52
  TOAST_DEFAULTS,
53
53
  } from "./toast-host.js";
54
54
 
55
- // REQ-NAV-STRUCTURE: the navigation-shape resolver both hosts draw their chrome
56
- // from. `normaliseNavigation` turns a stored `theme_config.navigation` block into
57
- // the resolved `{ menuType }` the chrome switches on; `menuItemCap` states how
58
- // many menu pages that shape may draw at once. One implementation, so a menu
59
- // type cannot mean one thing in the Player and another in the Expo export.
60
- // `resolveFooterTokens` is the footer strip's own token set, falling back to
61
- // the sidebar's so an untouched workspace keeps the appearance it has. It
62
- // exists because the strip IS the menu under `bottom-tabs`, where the sidebar
63
- // panel is hidden and those tokens can no longer be set at all.
55
+ // REQ-NAV-STRUCTURE: the navigation resolver both hosts draw their chrome from.
56
+ // `normaliseNavigation` turns a stored `theme_config.navigation` block into the
57
+ // resolved `{ menuType }` — always `sidebar` since sc-7352 retired the other
58
+ // shapes, so a stored value never needs guarding; `menuItemCap` / `quickBarCap`
59
+ // state the caps. One implementation, so the chrome cannot mean one thing in
60
+ // the Player and another in the Expo export. `resolveFooterTokens` is the quick
61
+ // bar's own token set, falling back to the sidebar's so an untouched workspace
62
+ // keeps the appearance it has.
64
63
  // REQ-THEME-14: `resolveSidebarTokens` and `resolveTopBarTokens` are the same
65
64
  // idea for the rail and the app bar, whose defaults used to live inline in each
66
65
  // host and had already drifted apart.
@@ -59,10 +59,8 @@ function normaliseNavigation(navigation) {
59
59
  }
60
60
 
61
61
  /**
62
- * How many MENU pages the chrome may draw at once, or `null` for no cap.
63
- *
64
- * Where the chrome IS the menu (`bottom-tabs`) this cap must never drop a page:
65
- * the surplus moves behind a More sheet. Contrast `quickBarCap` below.
62
+ * How many MENU pages the chrome may draw at once, or `null` for no cap. The
63
+ * sidebar has none. Contrast `quickBarCap` below.
66
64
  *
67
65
  * @param {string} menuType — a resolved menu type.
68
66
  * @returns {number|null}
@@ -90,13 +88,17 @@ function quickBarCap(menuType) {
90
88
  }
91
89
 
92
90
  // The strip's surface was hard-coded white on BOTH hosts and its items read the
93
- // sidebar's tokens — which is fine while it is the sidebar's secondary quick bar
94
- // and fatal once it IS the menu (`bottom-tabs` hides the sidebar panel, so those
95
- // tokens have nowhere to be set). `footer` gives it tokens of its own.
91
+ // sidebar's tokens. `footer` gives the quick bar tokens of its own.
92
+ //
93
+ // Every colour falls back to the sidebar's, so a workspace that never opens the
94
+ // panel keeps exactly the appearance it has today; only an explicit `footer`
95
+ // value moves anything.
96
96
  //
97
- // Every field falls back to the sidebar's, so a workspace that never opens the
98
- // Footer panel keeps exactly the appearance it has today; only an explicit
99
- // `footer` value moves anything.
97
+ // sc-7695: this block is the BOTTOM EDGE's, not the quick bar's alone. A page's
98
+ // own bottom bar (REQ-NAV-LOCAL) is the same strip worn by one page and reads
99
+ // exactly these values — the Design panel that edits them is named for that
100
+ // bar, since the quick bar is on its way out (sc-7753) and the look outlives
101
+ // it. No key is the bar's alone: two strips, one look.
100
102
  function resolveFooterTokens(theme) {
101
103
  const config = isPlainObject(theme) ? theme : {};
102
104
  const footer = isPlainObject(config.footer) ? config.footer : {};
@@ -176,11 +178,6 @@ function pageColor(config, alphaRule) {
176
178
  );
177
179
  }
178
180
 
179
- // An `attached` tab: opaque, or the bar shows through the page it joins.
180
- function pageSurface(config) {
181
- return pageColor(config, opaqueOr);
182
- }
183
-
184
181
  // sc-6596: what an UNSET rail or bar is painted. Read at full strength — an
185
182
  // alpha on the bottom layer would mean nothing to chrome above it.
186
183
  function chromeSurface(config) {
@@ -223,12 +220,6 @@ function tabIndicatorOr(value) {
223
220
  return Math.min(Math.max(Math.round(value), 0), TAB_INDICATOR_MAX);
224
221
  }
225
222
 
226
- function opaqueOr(value) {
227
- return isHexColor(value) && value.length !== 9 && value.length !== 5
228
- ? value
229
- : null;
230
- }
231
-
232
223
  // A gradient stop as the page readers admit one: flat hex, never alpha.
233
224
  function flatHexOrNull(value) {
234
225
  return isHexColor(value) && value.length !== 9 ? value : null;
@@ -330,18 +321,15 @@ function resolveTopBarTokens(theme) {
330
321
  // surface and meets the page beneath it, so the two read as one plane. Meaningless on a shape that draws no tab row — a host
331
322
  // reads it only where it has tabs to draw.
332
323
  tabStyle: topBar.tabStyle === "attached" ? "attached" : "underline",
333
- // The surface an `attached` tab AND its content panel share. One value for
334
- // both, so the join cannot come apart: whatever the current tab is painted,
335
- // the page beneath it is painted too. An authored active surface therefore
336
- // moves both. Resolved HERE rather than by each host, because a tab that
337
- // fails to match the page it is joined to reads as a mismatched box instead
338
- // of a tab, and two hosts computing it separately is exactly how that
339
- // drifts. Opaque by construction: a translucent tab would show the bar
340
- // through the page it claims to be part of.
341
- contentSurface: hexOr(
342
- opaqueOr(topBar.tabActiveBackgroundColor),
343
- pageSurface(config),
344
- ),
324
+ // The surface the PANEL behind an `attached` row paints — the authored
325
+ // active colour, alpha and all, or nothing. Under `attached` the current
326
+ // tab itself paints NOTHING: it is a hole in the bar, and what shows
327
+ // through it is this panel, or the page's own wash when this is null. So
328
+ // the tab is exactly as see-through as the page, a gradient stays one
329
+ // gradient, and a translucent page never doubles up under a tab (sc-7352 —
330
+ // painting the tab a copy of the page could never do any of that). One
331
+ // value, resolved HERE, so both hosts paint the panel the same colour.
332
+ contentSurface: hexOrNull(topBar.tabActiveBackgroundColor),
345
333
  // REQ-NAV-STRUCTURE: the underline that marks the current tab, in px. Zero
346
334
  // turns it OFF and leaves the tab marked by its label colour alone, which
347
335
  // is a real choice rather than an unstyled state — so unlike the chrome
@@ -360,9 +348,9 @@ function resolveTopBarTokens(theme) {
360
348
  // and the bar shows through it, which is what the row did before the keys
361
349
  // existed — so an app that never set them is unchanged.
362
350
  tabBackgroundColor: hexOrNull(topBar.tabBackgroundColor),
363
- // Only meaningful where the style draws no panel: under `attached` the
364
- // active surface IS `contentSurface` above. Null under `underline` leaves
365
- // the current tab unpainted, marked by its indicator alone.
351
+ // Under `underline` the current tab's own surface, or null for none. Under
352
+ // `attached` the tab paints nothing and this colour is the PANEL's — see
353
+ // `contentSurface` above.
366
354
  tabActiveBackgroundColor: hexOrNull(topBar.tabActiveBackgroundColor),
367
355
  };
368
356
  }
@@ -50,10 +50,8 @@ export function normaliseNavigation(navigation) {
50
50
  }
51
51
 
52
52
  /**
53
- * How many MENU pages the chrome may draw at once, or `null` for no cap.
54
- *
55
- * Where the chrome IS the menu (`bottom-tabs`) this cap must never drop a page:
56
- * the surplus moves behind a More sheet. Contrast `quickBarCap` below.
53
+ * How many MENU pages the chrome may draw at once, or `null` for no cap. The
54
+ * sidebar has none. Contrast `quickBarCap` below.
57
55
  *
58
56
  * @param {string} menuType — a resolved menu type.
59
57
  * @returns {number|null}
@@ -81,13 +79,17 @@ export function quickBarCap(menuType) {
81
79
  }
82
80
 
83
81
  // The strip's surface was hard-coded white on BOTH hosts and its items read the
84
- // sidebar's tokens — which is fine while it is the sidebar's secondary quick bar
85
- // and fatal once it IS the menu (`bottom-tabs` hides the sidebar panel, so those
86
- // tokens have nowhere to be set). `footer` gives it tokens of its own.
82
+ // sidebar's tokens. `footer` gives the quick bar tokens of its own.
83
+ //
84
+ // Every colour falls back to the sidebar's, so a workspace that never opens the
85
+ // panel keeps exactly the appearance it has today; only an explicit `footer`
86
+ // value moves anything.
87
87
  //
88
- // Every field falls back to the sidebar's, so a workspace that never opens the
89
- // Footer panel keeps exactly the appearance it has today; only an explicit
90
- // `footer` value moves anything.
88
+ // sc-7695: this block is the BOTTOM EDGE's, not the quick bar's alone. A page's
89
+ // own bottom bar (REQ-NAV-LOCAL) is the same strip worn by one page and reads
90
+ // exactly these values — the Design panel that edits them is named for that
91
+ // bar, since the quick bar is on its way out (sc-7753) and the look outlives
92
+ // it. No key is the bar's alone: two strips, one look.
91
93
  export function resolveFooterTokens(theme) {
92
94
  const config = isPlainObject(theme) ? theme : {};
93
95
  const footer = isPlainObject(config.footer) ? config.footer : {};
@@ -167,11 +169,6 @@ function pageColor(config, alphaRule) {
167
169
  );
168
170
  }
169
171
 
170
- // An `attached` tab: opaque, or the bar shows through the page it joins.
171
- function pageSurface(config) {
172
- return pageColor(config, opaqueOr);
173
- }
174
-
175
172
  // sc-6596: what an UNSET rail or bar is painted. Read at full strength — an
176
173
  // alpha on the bottom layer would mean nothing to chrome above it.
177
174
  function chromeSurface(config) {
@@ -214,12 +211,6 @@ function tabIndicatorOr(value) {
214
211
  return Math.min(Math.max(Math.round(value), 0), TAB_INDICATOR_MAX);
215
212
  }
216
213
 
217
- function opaqueOr(value) {
218
- return isHexColor(value) && value.length !== 9 && value.length !== 5
219
- ? value
220
- : null;
221
- }
222
-
223
214
  // A gradient stop as the page readers admit one: flat hex, never alpha.
224
215
  function flatHexOrNull(value) {
225
216
  return isHexColor(value) && value.length !== 9 ? value : null;
@@ -321,18 +312,15 @@ export function resolveTopBarTokens(theme) {
321
312
  // surface and meets the page beneath it, so the two read as one plane. Meaningless on a shape that draws no tab row — a host
322
313
  // reads it only where it has tabs to draw.
323
314
  tabStyle: topBar.tabStyle === "attached" ? "attached" : "underline",
324
- // The surface an `attached` tab AND its content panel share. One value for
325
- // both, so the join cannot come apart: whatever the current tab is painted,
326
- // the page beneath it is painted too. An authored active surface therefore
327
- // moves both. Resolved HERE rather than by each host, because a tab that
328
- // fails to match the page it is joined to reads as a mismatched box instead
329
- // of a tab, and two hosts computing it separately is exactly how that
330
- // drifts. Opaque by construction: a translucent tab would show the bar
331
- // through the page it claims to be part of.
332
- contentSurface: hexOr(
333
- opaqueOr(topBar.tabActiveBackgroundColor),
334
- pageSurface(config),
335
- ),
315
+ // The surface the PANEL behind an `attached` row paints — the authored
316
+ // active colour, alpha and all, or nothing. Under `attached` the current
317
+ // tab itself paints NOTHING: it is a hole in the bar, and what shows
318
+ // through it is this panel, or the page's own wash when this is null. So
319
+ // the tab is exactly as see-through as the page, a gradient stays one
320
+ // gradient, and a translucent page never doubles up under a tab (sc-7352 —
321
+ // painting the tab a copy of the page could never do any of that). One
322
+ // value, resolved HERE, so both hosts paint the panel the same colour.
323
+ contentSurface: hexOrNull(topBar.tabActiveBackgroundColor),
336
324
  // REQ-NAV-STRUCTURE: the underline that marks the current tab, in px. Zero
337
325
  // turns it OFF and leaves the tab marked by its label colour alone, which
338
326
  // is a real choice rather than an unstyled state — so unlike the chrome
@@ -351,9 +339,9 @@ export function resolveTopBarTokens(theme) {
351
339
  // and the bar shows through it, which is what the row did before the keys
352
340
  // existed — so an app that never set them is unchanged.
353
341
  tabBackgroundColor: hexOrNull(topBar.tabBackgroundColor),
354
- // Only meaningful where the style draws no panel: under `attached` the
355
- // active surface IS `contentSurface` above. Null under `underline` leaves
356
- // the current tab unpainted, marked by its indicator alone.
342
+ // Under `underline` the current tab's own surface, or null for none. Under
343
+ // `attached` the tab paints nothing and this colour is the PANEL's — see
344
+ // `contentSurface` above.
357
345
  tabActiveBackgroundColor: hexOrNull(topBar.tabActiveBackgroundColor),
358
346
  };
359
347
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.140.0",
3
+ "version": "0.141.0",
4
4
  "description": "Common widget interface for AppStudio. Implements WidgetManifest, WidgetContext, property schema, and helper hooks.",
5
5
  "homepage": "https://github.com/Colix-AB/AppStudio",
6
6
  "type": "module",