@nodii/dev 0.6.15 → 0.6.17

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/dist/figma.js CHANGED
@@ -106,10 +106,10 @@ export function figmaChangeSetText(cs) {
106
106
  }
107
107
 
108
108
  // Scoped design locks (DEC-0032, MW1-57.6 to 57.9). A scope is written
109
- // kind:id on the command line: component:NODE, page:NODE, foundations, or
110
- // create:PAGE:NAME. A node id from a figma.com link (12-34) is Figma's 12:34.
109
+ // kind:id on the command line: component:NODE, page:NODE, section:NODE,
110
+ // foundations, or create:PAGE:NAME. A node id from a figma.com link (12-34) is Figma's 12:34.
111
111
  const nodeId = (text) => (/^\d+-\d+$/.test(text) ? text.replace("-", ":") : text);
112
- const SCOPE_USAGE = "--scope component:NODE_ID | page:NODE_ID | foundations | create:PAGE_ID:NAME";
112
+ const SCOPE_USAGE = "--scope component:NODE_ID | page:NODE_ID | section:NODE_ID | foundations | create:PAGE_ID:NAME";
113
113
 
114
114
  export function parseScope(value) {
115
115
  const text = typeof value === "string" ? value.trim() : "";
@@ -119,7 +119,7 @@ export function parseScope(value) {
119
119
  if (create && create[2].trim() && create[2].trim().length <= 200)
120
120
  return { kind: "create", pageId: nodeId(create[1]), name: create[2].trim() };
121
121
  // Dev takes a scope at a top-level node: a plain id, never an instance's (I1:2;3:4).
122
- const node = text.match(/^(component|page):(\d+[:-]\d+)$/);
122
+ const node = text.match(/^(component|page|section):(\d+[:-]\d+)$/);
123
123
  if (!node) throw bad();
124
124
  return { kind: node[1], nodeId: nodeId(node[2]) };
125
125
  }
@@ -26,7 +26,7 @@
26
26
  // it until it returns [] (each round can reveal instances inside slot
27
27
  // content).
28
28
 
29
- export const VERSION = "1.0.2";
29
+ export const VERSION = "1.0.3";
30
30
 
31
31
  /** @typedef {{ nodeId: string, check: string, detail: string }} Finding */
32
32
  /**
@@ -86,6 +86,9 @@ const ALLOWED_FIELDS = new Set([
86
86
  "exportSettings",
87
87
  "pluginData",
88
88
  "sharedPluginData",
89
+ // Choosing one of the library's variable modes (a size or density mode)
90
+ // is a mode, like a variant, not a look override.
91
+ "explicitVariableModes",
89
92
  "minWidth",
90
93
  "maxWidth",
91
94
  "minHeight",
@@ -123,6 +126,10 @@ const EXTERNAL_PLANE = /customer portal|partner room|external/i;
123
126
  const SELF_CONTAINER =
124
127
  /sidebar ?footer|user button|user menu|account menu|profile/i;
125
128
  const SELF_TEXT = /\(you\)|^you$/i;
129
+ const SIGNED_IN = /^(signed in( as\b.*)?|not you\?.*)$/i;
130
+ // The signed-in account card (Account Context, kind=Account) shows the
131
+ // viewer's own email.
132
+ const SELF_COMPONENT = /^account context$/i;
126
133
  const NOT_A_PERSON = /^(you|the team)$/i;
127
134
  const MASKED = /[*•●]/;
128
135
  const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g;
@@ -206,6 +213,14 @@ function fieldValue(node, field, styles) {
206
213
  return styleKeys(node, styles);
207
214
  if (TEXT_STYLE_FIELDS.has(field) || field.startsWith("lineHeight"))
208
215
  return node?.style?.[field];
216
+ // An image fill's picture is content (DEC-0048): only its scale mode,
217
+ // filters and opacity are look, so imageRef is left out of the comparison.
218
+ if (field === "fills" || field === "background")
219
+ return (node?.[field] ?? []).map((p) => {
220
+ if (p?.type !== "IMAGE") return p;
221
+ const { imageRef: _picture, gifRef: _gif, ...look } = p;
222
+ return look;
223
+ });
209
224
  // A binding is judged per bound field: (un)binding a field the instance
210
225
  // may override anyway (spacing, size) is that override, not a visual one.
211
226
  if (field === "boundVariables") {
@@ -365,8 +380,9 @@ function headerCell(a, inst) {
365
380
  const c = a.componentOf(inst);
366
381
  const set = a.setOf(inst);
367
382
  if (/^table header$/i.test(set?.name ?? c?.name ?? "")) return true;
383
+ // Exactly a header part: a Command list's part=group-heading has no fill.
368
384
  const part = inst.componentProperties?.part?.value;
369
- if (typeof part === "string" && /head/i.test(part)) return true;
385
+ if (typeof part === "string" && /^head(er)?$/i.test(part)) return true;
370
386
  return /(^|,\s*)part=head(er)?(\s*,|$)/i.test(c?.name ?? "");
371
387
  }
372
388
 
@@ -776,19 +792,22 @@ const ICON = /^(icon|lucide)\//i;
776
792
  /**
777
793
  * Design-system rulings on paint overrides: an icon takes its colour from
778
794
  * where it sits (currentColor), so a bound fill or stroke on an Icon/ or
779
- * lucide/ instance or its vectors is allowed (a Logo/ is not); a PiiText in
795
+ * lucide/ instance or its vectors is allowed (a Logo/ is not), and so is its
796
+ * stroke weight, which scales with the icon's size; a PiiText in
780
797
  * mode=revealed inherits ink, so its fill may be set.
781
798
  */
782
799
  function exemptPaint(a, from, node, field) {
783
- if (field !== "fills" && field !== "strokes") return false;
800
+ if (field !== "fills" && field !== "strokes" && field !== "strokeWeight")
801
+ return false;
784
802
  const path = [node, ...a.ancestors(node)];
785
803
  const upTo = path.slice(0, path.indexOf(from) + 1);
804
+ const inIcon = upTo.some(
805
+ (x) => x.type === "INSTANCE" && ICON.test(a.componentLabel(x)),
806
+ );
807
+ // An icon's stroke weight scales with the size it is placed at.
808
+ if (field === "strokeWeight") return inIcon;
786
809
  const bound = (node[field] ?? []).every((p) => p?.boundVariables?.color);
787
- if (
788
- bound &&
789
- upTo.some((x) => x.type === "INSTANCE" && ICON.test(a.componentLabel(x)))
790
- )
791
- return true;
810
+ if (bound && inIcon) return true;
792
811
  return field === "fills" && revealedByPermission(a, node);
793
812
  }
794
813
  const paintColours = (paints) =>
@@ -847,10 +866,34 @@ function handDrawnButton(n) {
847
866
  function isSelf(a, n) {
848
867
  const up = a.ancestors(n);
849
868
  if (up.some((x) => SELF_CONTAINER.test(x.name ?? ""))) return true;
869
+ const accountCard = (x) => {
870
+ const set =
871
+ x.type === "INSTANCE"
872
+ ? a.componentLabel(x)
873
+ : x.type === "COMPONENT"
874
+ ? a.parent.get(x.id)?.name
875
+ : null;
876
+ if (!SELF_COMPONENT.test(set ?? "")) return false;
877
+ const name = x.type === "INSTANCE" ? a.componentOf(x)?.name : x.name;
878
+ const kind = x.componentProperties?.kind?.value;
879
+ return (
880
+ kind === "Account" || /(^|,\s*)kind=account(\s*,|$)/i.test(name ?? "")
881
+ );
882
+ };
883
+ if (up.some(accountCard)) return true;
850
884
  const siblings = a.parent.get(n.id)?.children ?? [];
851
- return [n, ...siblings].some(
852
- (x) => x.type === "TEXT" && SELF_TEXT.test(textOf(x)),
853
- );
885
+ if (
886
+ [n, ...siblings].some((x) => x.type === "TEXT" && SELF_TEXT.test(textOf(x)))
887
+ )
888
+ return true;
889
+ // "Signed in" or "Not you?" in the same card: the nearest instance or
890
+ // component holding it, never a whole row or screen.
891
+ const card = up.find((x) => x.type === "INSTANCE" || x.type === "COMPONENT");
892
+ return card
893
+ ? [...indexTree(card).byId.values()].some(
894
+ (x) => x.type === "TEXT" && SIGNED_IN.test(textOf(x)),
895
+ )
896
+ : false;
854
897
  }
855
898
  function revealedByPermission(a, n) {
856
899
  return [n, ...a.ancestors(n)].some((x) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodii/dev",
3
- "version": "0.6.15",
3
+ "version": "0.6.17",
4
4
  "description": "Agent CLI for Nodii Dev, reached through the platform edge",
5
5
  "type": "module",
6
6
  "bin": {