@colixsystems/widget-sdk 0.164.0 → 0.165.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
@@ -73,7 +73,11 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
73
73
 
74
74
  ## Status
75
75
 
76
- `v0.164.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**.
76
+ `v0.165.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**.
77
+
78
+ ### What's new in 0.165.0 (contract 1.127.0 — unchanged)
79
+
80
+ **`no-hardcoded-design` flags a literal corner radius.** A radius is the app's shape: `theme.radii`, Theme Settings and the SDK's `Card` / `Button` / `ListRow` / `Tabs` all carry it, so a `borderRadius: 12` pinned in a widget is exactly where two widgets in one app stop matching. The rule now warns on a literal non-zero radius on any corner spelling; read it from `theme.radii.*` or a style field, or draw the surface with the SDK component. A computed radius (`size / 2` for a circle) and `0` are not flagged, and `// appstudio-design-ok: <reason>` licenses a genuine exception. Still a warning for a human author; like every linter finding it blocks the AI widget agent's publish, which is how generated widgets are held to it.
77
81
 
78
82
  ### What's new in 0.164.0 (contract 1.127.0)
79
83
 
@@ -827,7 +831,7 @@ Additive — one new hook, one new optional context-slice member; no existing ex
827
831
 
828
832
  **Two linter rules make the styling contract checkable, and `lintStyleWiring` joins the linter export (sc-6455).** Every visual value a widget writes reaches the app's owner through one of exactly two channels — a **theme token** (`useTheme()`), which is the app-wide default and follows a look change, or a **`styleSchema` field** read off `props.style`, which the Studio offers per instance in the widget editor *and* app-wide under **Design → Widget appearance**. A literal reaches neither: it outranks the theme permanently and no control on either surface can move it, so the owner finds a corner of their app they cannot restyle. Until now that rule was documentation only.
829
833
 
830
- - **`no-hardcoded-design`** flags a colour literal (`#rgb` / `#rrggbb` / `#rrggbbaa`, `rgb()`, `rgba()`, `hsl()`, `hsla()`), a `fontFamily` string literal, or a numeric `fontSize`. A `fontSize` resolved off a theme token or a style field (`style.valueSize ?? 18`) is *not* flagged — a literal `default` beside a declared field is the contract working. Raw `padding` / `margin` / `borderRadius` numbers are deliberately out of scope: measured layout legitimately carries them, so a spacing rule would be noise.
834
+ - **`no-hardcoded-design`** flags a colour literal (`#rgb` / `#rrggbb` / `#rrggbbaa`, `rgb()`, `rgba()`, `hsl()`, `hsla()`), a `fontFamily` string literal, a numeric `fontSize`, or (since 0.165.0) a literal non-zero corner radius (`borderRadius: 12`, any per-corner spelling). A `fontSize` resolved off a theme token or a style field (`style.valueSize ?? 18`) is *not* flagged — a literal `default` beside a declared field is the contract working — and neither is a computed radius (`size / 2`) or `0`. Raw `padding` / `margin` numbers are deliberately out of scope: measured layout legitimately carries them, so a spacing rule would be noise.
831
835
  - **`style-field-unread`** flags a `styleSchema` field whose name appears nowhere in the widget's source — a dead control the author moves to no effect. It runs over the WHOLE bundle, not per file, so a split-impl widget that reads a field in `widget.web.jsx` and not in `widget.native.jsx` is correctly counted as wired.
832
836
 
833
837
  A value that genuinely cannot be a token — a categorical series palette, a video letterbox, a scannable QR plate — is licensed with a preceding comment. **The reason is mandatory**; a bare marker licenses nothing. A marker on its own line covers the whole statement below it (bracket-balanced, so one marker covers a multi-line palette); a trailing marker covers only its own line.
package/dist/linter.cjs CHANGED
@@ -1337,16 +1337,20 @@ function narrowManifestForFile(manifest, filename) {
1337
1337
  // colour or font pinned in the source outranks BOTH permanently, so neither
1338
1338
  // the workspace theme nor Design -> Widget appearance can ever reach it.
1339
1339
  //
1340
- // Deliberately narrow: colour literals, a `fontFamily` string, and a bare
1341
- // numeric `fontSize`. Raw padding/margin/borderRadius numbers are NOT flagged
1342
- // — measured layout legitimately carries them (a `flexBasis` cell width the
1343
- // designer skill itself teaches), so a spacing rule would be noise that
1344
- // devalues the three unambiguous ones.
1340
+ // Deliberately narrow: colour literals, a `fontFamily` string, a bare numeric
1341
+ // `fontSize`, and a literal non-zero corner radius. A radius is the app's
1342
+ // shape — `theme.radii`, Theme Settings, the SDK components all carry it — so
1343
+ // a pinned one is the mismatch between two widgets. Raw padding/margin numbers
1344
+ // are NOT flagged: measured layout legitimately carries them (a `flexBasis`
1345
+ // cell width the designer skill itself teaches), and a computed radius
1346
+ // (`size / 2`, a circle) is geometry, not a look.
1345
1347
  const _DESIGN_HEX_RE =
1346
1348
  /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})(?![0-9a-fA-F])/;
1347
1349
  const _DESIGN_FUNC_COLOR_RE = /\b(?:rgba?|hsla?)\s*\(/;
1348
1350
  const _DESIGN_FONT_FAMILY_RE = /\bfontFamily\s*:\s*["'`]\s*\S/;
1349
1351
  const _DESIGN_FONT_SIZE_RE = /\bfontSize\s*:\s*-?\d/;
1352
+ const _DESIGN_RADIUS_RE =
1353
+ /\bborder(?:Top|Bottom|Start|End)?(?:Left|Right|Start|End)?Radius\s*:\s*(?:0*[1-9]\d*(?:\.\d+)?|0\.\d*[1-9]\d*)\s*(?=[,}]|$)/;
1350
1354
  // A line that already reads the theme or the author's style object is
1351
1355
  // resolving a DECLARED fallback, not pinning a look — `style.valueSize ?? 18`
1352
1356
  // is exactly what a styleSchema `default` is for. Colour literals are held to
@@ -1411,6 +1415,8 @@ function _hardcodedDesignRules(source) {
1411
1415
  !_DESIGN_TOKEN_REF_RE.test(line)
1412
1416
  ) {
1413
1417
  what = "a pixel font size";
1418
+ } else if (_DESIGN_RADIUS_RE.test(line)) {
1419
+ what = "a pixel corner radius";
1414
1420
  }
1415
1421
  if (!what) continue;
1416
1422
  findings.push({
@@ -1419,7 +1425,8 @@ function _hardcodedDesignRules(source) {
1419
1425
  label:
1420
1426
  `${what} pinned in the source is beyond the reach of BOTH the ` +
1421
1427
  `workspace theme and the author's Style controls. Read it from ` +
1422
- `useTheme() (theme.colors.* / theme.typography.*), or declare a ` +
1428
+ `useTheme() (theme.colors.* / theme.typography.* / theme.radii.*), ` +
1429
+ `use the SDK's <Card> / <Button> / <ListRow>, or declare a ` +
1423
1430
  `styleSchema field and apply props.style.<field>. License a value ` +
1424
1431
  `that truly cannot be a token — a series palette, a video ` +
1425
1432
  `letterbox, a QR plate — with a preceding ` +
package/dist/linter.js CHANGED
@@ -1408,16 +1408,20 @@ export function narrowManifestForFile(manifest, filename) {
1408
1408
  // colour or font pinned in the source outranks BOTH permanently, so neither
1409
1409
  // the workspace theme nor Design -> Widget appearance can ever reach it.
1410
1410
  //
1411
- // Deliberately narrow: colour literals, a `fontFamily` string, and a bare
1412
- // numeric `fontSize`. Raw padding/margin/borderRadius numbers are NOT flagged
1413
- // — measured layout legitimately carries them (a `flexBasis` cell width the
1414
- // designer skill itself teaches), so a spacing rule would be noise that
1415
- // devalues the three unambiguous ones.
1411
+ // Deliberately narrow: colour literals, a `fontFamily` string, a bare numeric
1412
+ // `fontSize`, and a literal non-zero corner radius. A radius is the app's
1413
+ // shape — `theme.radii`, Theme Settings, the SDK components all carry it — so
1414
+ // a pinned one is the mismatch between two widgets. Raw padding/margin numbers
1415
+ // are NOT flagged: measured layout legitimately carries them (a `flexBasis`
1416
+ // cell width the designer skill itself teaches), and a computed radius
1417
+ // (`size / 2`, a circle) is geometry, not a look.
1416
1418
  const _DESIGN_HEX_RE =
1417
1419
  /#(?:[0-9a-fA-F]{8}|[0-9a-fA-F]{6}|[0-9a-fA-F]{3,4})(?![0-9a-fA-F])/;
1418
1420
  const _DESIGN_FUNC_COLOR_RE = /\b(?:rgba?|hsla?)\s*\(/;
1419
1421
  const _DESIGN_FONT_FAMILY_RE = /\bfontFamily\s*:\s*["'`]\s*\S/;
1420
1422
  const _DESIGN_FONT_SIZE_RE = /\bfontSize\s*:\s*-?\d/;
1423
+ const _DESIGN_RADIUS_RE =
1424
+ /\bborder(?:Top|Bottom|Start|End)?(?:Left|Right|Start|End)?Radius\s*:\s*(?:0*[1-9]\d*(?:\.\d+)?|0\.\d*[1-9]\d*)\s*(?=[,}]|$)/;
1421
1425
  // A line that already reads the theme or the author's style object is
1422
1426
  // resolving a DECLARED fallback, not pinning a look — `style.valueSize ?? 18`
1423
1427
  // is exactly what a styleSchema `default` is for. Colour literals are held to
@@ -1482,6 +1486,8 @@ function _hardcodedDesignRules(source) {
1482
1486
  !_DESIGN_TOKEN_REF_RE.test(line)
1483
1487
  ) {
1484
1488
  what = "a pixel font size";
1489
+ } else if (_DESIGN_RADIUS_RE.test(line)) {
1490
+ what = "a pixel corner radius";
1485
1491
  }
1486
1492
  if (!what) continue;
1487
1493
  findings.push({
@@ -1490,7 +1496,8 @@ function _hardcodedDesignRules(source) {
1490
1496
  label:
1491
1497
  `${what} pinned in the source is beyond the reach of BOTH the ` +
1492
1498
  `workspace theme and the author's Style controls. Read it from ` +
1493
- `useTheme() (theme.colors.* / theme.typography.*), or declare a ` +
1499
+ `useTheme() (theme.colors.* / theme.typography.* / theme.radii.*), ` +
1500
+ `use the SDK's <Card> / <Button> / <ListRow>, or declare a ` +
1494
1501
  `styleSchema field and apply props.style.<field>. License a value ` +
1495
1502
  `that truly cannot be a token — a series palette, a video ` +
1496
1503
  `letterbox, a QR plate — with a preceding ` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colixsystems/widget-sdk",
3
- "version": "0.164.0",
3
+ "version": "0.165.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",