@nadicodeai/ui 8.5.0 → 9.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.
Files changed (76) hide show
  1. package/AGENTS.md +1 -1
  2. package/LICENSES/THINKING-ORBS-MIT.txt +21 -0
  3. package/NOTICE +7 -10
  4. package/dist/components/agent-orb-appearance.d.ts +54 -0
  5. package/dist/components/agent-orb-appearance.d.ts.map +1 -0
  6. package/dist/components/agent-orb-appearance.js +87 -0
  7. package/dist/components/agent-orb.d.ts +5 -3
  8. package/dist/components/agent-orb.d.ts.map +1 -1
  9. package/dist/eslint/index.js +4 -0
  10. package/dist/eslint/rules/expect-chain.js +60 -0
  11. package/dist/eslint/rules/no-existence-only-assertion.js +89 -0
  12. package/dist/eslint/rules/no-subject-derived-expectation.js +129 -0
  13. package/dist/index.d.ts +0 -2
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +0 -2
  16. package/dist/internal/agent-orb-motion.d.ts +3 -3
  17. package/dist/internal/agent-orb-motion.d.ts.map +1 -1
  18. package/dist/internal/agent-orb-motion.js +1 -0
  19. package/dist/internal/agent-orb.d.ts +11 -1
  20. package/dist/internal/agent-orb.d.ts.map +1 -1
  21. package/dist/internal/agent-orb.js +80 -5
  22. package/docs/agent-orb.md +47 -14
  23. package/docs/contract.md +8 -29
  24. package/docs/migration/9.0.0.md +35 -0
  25. package/package.json +2 -3
  26. package/LICENSES/PAPER-SHADERS-APACHE-2.0.txt +0 -186
  27. package/dist/components/agent-visual.d.ts +0 -17
  28. package/dist/components/agent-visual.d.ts.map +0 -1
  29. package/dist/components/agent-visual.js +0 -63
  30. package/dist/components/forma-appearance.d.ts +0 -13
  31. package/dist/components/forma-appearance.d.ts.map +0 -1
  32. package/dist/components/forma-appearance.js +0 -11
  33. package/dist/components/forma-avatar.d.ts +0 -23
  34. package/dist/components/forma-avatar.d.ts.map +0 -1
  35. package/dist/components/forma-avatar.js +0 -95
  36. package/dist/internal/agent-visual/color.d.ts +0 -4
  37. package/dist/internal/agent-visual/color.d.ts.map +0 -1
  38. package/dist/internal/agent-visual/color.js +0 -78
  39. package/dist/internal/agent-visual/poster.d.ts +0 -6
  40. package/dist/internal/agent-visual/poster.d.ts.map +0 -1
  41. package/dist/internal/agent-visual/poster.js +0 -35
  42. package/dist/internal/agent-visual/recipes.d.ts +0 -15
  43. package/dist/internal/agent-visual/recipes.d.ts.map +0 -1
  44. package/dist/internal/agent-visual/recipes.js +0 -72
  45. package/dist/internal/agent-visual/renderer.d.ts +0 -10
  46. package/dist/internal/agent-visual/renderer.d.ts.map +0 -1
  47. package/dist/internal/agent-visual/renderer.js +0 -112
  48. package/dist/internal/agent-visual/shader.d.ts +0 -2
  49. package/dist/internal/agent-visual/shader.d.ts.map +0 -1
  50. package/dist/internal/agent-visual/shader.js +0 -158
  51. package/dist/internal/forma/frame.d.ts +0 -10
  52. package/dist/internal/forma/frame.d.ts.map +0 -1
  53. package/dist/internal/forma/frame.js +0 -211
  54. package/dist/internal/forma/geometry.d.ts +0 -41
  55. package/dist/internal/forma/geometry.d.ts.map +0 -1
  56. package/dist/internal/forma/geometry.js +0 -235
  57. package/dist/internal/forma/hallmark.d.ts +0 -5
  58. package/dist/internal/forma/hallmark.d.ts.map +0 -1
  59. package/dist/internal/forma/hallmark.js +0 -4
  60. package/dist/internal/forma/model.d.ts +0 -53
  61. package/dist/internal/forma/model.d.ts.map +0 -1
  62. package/dist/internal/forma/model.js +0 -70
  63. package/dist/internal/forma/motion.d.ts +0 -42
  64. package/dist/internal/forma/motion.d.ts.map +0 -1
  65. package/dist/internal/forma/motion.js +0 -194
  66. package/dist/internal/forma/rig.d.ts +0 -10
  67. package/dist/internal/forma/rig.d.ts.map +0 -1
  68. package/dist/internal/forma/rig.js +0 -211
  69. package/dist/internal/forma/still.d.ts +0 -4
  70. package/dist/internal/forma/still.d.ts.map +0 -1
  71. package/dist/internal/forma/still.js +0 -8
  72. package/dist/internal/forma/stored-appearance.d.ts +0 -29
  73. package/dist/internal/forma/stored-appearance.d.ts.map +0 -1
  74. package/dist/internal/forma/stored-appearance.js +0 -53
  75. package/docs/agent-visual.md +0 -114
  76. package/docs/forma-avatar.md +0 -110
package/AGENTS.md CHANGED
@@ -7,7 +7,7 @@ exports.
7
7
  - Component changes and admission: `docs/contract.md`.
8
8
  - Consumer setup: `docs/consuming-cross-repo.md`.
9
9
  - Tokens, CSS, motion, and assets: `../design-system/DESIGN.md`.
10
- - Agent visuals: `docs/agent-visual.md`; the Forma avatars: `docs/forma-avatar.md`.
10
+ - Agent identity, the orbs and their stored appearance: `docs/agent-orb.md`.
11
11
  - System activity indicators: `docs/activity.md`.
12
12
  - What belongs in this package and what belongs in a product: `docs/contract.md`,
13
13
  "Placement". Nadia's entry screen is the worked example that moved out.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jakub Antalik
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE CHANGED
@@ -1,15 +1,12 @@
1
- @nadicodeai/ui includes a modified derivative of Paper Shaders 0.0.78.
1
+ @nadicodeai/ui includes an adapted copy of the ribbon geometry from
2
+ thinking-orbs 0.3.1.
2
3
 
3
- Paper Shaders
4
- Copyright 2026 Paper
4
+ thinking-orbs
5
+ Copyright (c) 2026 Jakub Antalik
5
6
 
6
- Powered by Paper Shaders:
7
- https://shaders.paper.design
8
-
9
- The modified shader preserves Paper's Metaballs field, motion, colour blending,
10
- and edge calculation while adding NadicodeAI's four approved clipped surface
11
- materials. The modified source is
12
- src/internal/agent-visual/shader.ts.
7
+ Licensed under the MIT License; the full text ships as
8
+ LICENSES/THINKING-ORBS-MIT.txt. The copy in src/internal/agent-orb.ts keeps each
9
+ dot's lane through the depth sort, so Nadia's striped ribbon holds its stripes.
13
10
 
14
11
  @nadicodeai/ui includes model-maker and model-family marks from the LobeHub icon
15
12
  set, generated from @lobehub/icons-static-svg 1.95.1.
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The stored appearance, reachable from a server. `agent-orb.tsx` is a
3
+ * `"use client"` module, and under React Server Components every export of one
4
+ * becomes a client reference, a re-exported pure function included, which a
5
+ * server caller can only fail to invoke. A product that resolves an Agent's
6
+ * look before it renders the orb (the Portal builds its rows that way) imports
7
+ * the resolver from here; the orb comes from `@nadicodeai/ui/components/agent-orb`,
8
+ * and neither module re-exports the other.
9
+ */
10
+ import type { AgentOrbFigure, AgentOrbForm } from "../internal/agent-orb.js";
11
+ export type { AgentOrbFigure, AgentOrbForm } from "../internal/agent-orb.js";
12
+ /** What Nadia stores for an Agent's look, and reports to the Portal. Both may be absent or unknown. */
13
+ export interface StoredAgentOrbAppearance {
14
+ shape?: string | null;
15
+ color?: string | null;
16
+ }
17
+ /** The seven forms a person may choose, in the order a picker offers them. */
18
+ export declare const agentOrbForms: readonly ["orbit", "ribbon", "lattice", "globe", "network", "braid", "morph"];
19
+ /**
20
+ * The colours a person may choose. Each clears 3:1 against both the white and
21
+ * the black canvas, so a chosen orb stays visible in either theme; giallo does
22
+ * not (1.6:1 on white) and is not offered. Absence of a colour is the default:
23
+ * the orb inherits the text colour.
24
+ */
25
+ export declare const agentOrbColors: readonly ["cobalto", "verde", "rosso", "arancio"];
26
+ export type AgentOrbColor = (typeof agentOrbColors)[number];
27
+ /**
28
+ * The hex each colour is stored as. Nadia keeps an Agent's colour as a CSS
29
+ * value for its own surfaces, so the stored spelling is a value rather than a
30
+ * token reference; `agent-orb-appearance.test.ts` holds this table to the
31
+ * shipped design-system tokens, and the ESLint colour-literal exemption in the
32
+ * root config names this file.
33
+ */
34
+ export declare const agentOrbColorValues: Record<AgentOrbColor, string>;
35
+ /** The design-system token each colour paints with. */
36
+ export declare const agentOrbColorTokens: Record<AgentOrbColor, string>;
37
+ /** The `shape` value a chosen form is stored as. */
38
+ export declare const shapeForOrb: (form: AgentOrbForm) => string;
39
+ /** The form an Agent wears until a person chooses one. */
40
+ export declare const defaultAgentOrbForm: AgentOrbForm;
41
+ export declare const defaultAgentOrbShape: string;
42
+ /**
43
+ * Resolve the stored strings to what `AgentOrb` draws. The root Agent is Nadia
44
+ * and wears her striped ribbon whatever is stored, since her look is fixed. An
45
+ * unknown shape is the default form; an unknown colour is undefined, which
46
+ * leaves the orb the inherited text colour.
47
+ */
48
+ export declare function resolveAgentOrb(stored: StoredAgentOrbAppearance, { root }?: {
49
+ root?: boolean;
50
+ }): {
51
+ form: AgentOrbFigure;
52
+ color: AgentOrbColor | undefined;
53
+ };
54
+ //# sourceMappingURL=agent-orb-appearance.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-orb-appearance.d.ts","sourceRoot":"","sources":["../../src/components/agent-orb-appearance.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzE,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAEzE,uGAAuG;AACvG,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAED,8EAA8E;AAC9E,eAAO,MAAM,aAAa,+EAQkB,CAAC;AAE7C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,mDAAoD,CAAC;AAChF,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,cAAc,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAK7D,CAAC;AAEF,uDAAuD;AACvD,eAAO,MAAM,mBAAmB,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAK7D,CAAC;AAEF,oDAAoD;AACpD,eAAO,MAAM,WAAW,GAAI,MAAM,YAAY,KAAG,MAAuB,CAAC;AAEzE,0DAA0D;AAC1D,eAAO,MAAM,mBAAmB,EAAE,YAAsB,CAAC;AACzD,eAAO,MAAM,oBAAoB,EAAE,MAAyC,CAAC;AAsB7E;;;;;GAKG;AACH,wBAAgB,eAAe,CAC7B,MAAM,EAAE,wBAAwB,EAChC,EAAE,IAAY,EAAE,GAAE;IAAE,IAAI,CAAC,EAAE,OAAO,CAAA;CAAO,GACxC;IAAE,IAAI,EAAE,cAAc,CAAC;IAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CAAA;CAAE,CAG5D"}
@@ -0,0 +1,87 @@
1
+ /** The seven forms a person may choose, in the order a picker offers them. */
2
+ export const agentOrbForms = [
3
+ "orbit",
4
+ "ribbon",
5
+ "lattice",
6
+ "globe",
7
+ "network",
8
+ "braid",
9
+ "morph",
10
+ ];
11
+ /**
12
+ * The colours a person may choose. Each clears 3:1 against both the white and
13
+ * the black canvas, so a chosen orb stays visible in either theme; giallo does
14
+ * not (1.6:1 on white) and is not offered. Absence of a colour is the default:
15
+ * the orb inherits the text colour.
16
+ */
17
+ export const agentOrbColors = ["cobalto", "verde", "rosso", "arancio"];
18
+ /**
19
+ * The hex each colour is stored as. Nadia keeps an Agent's colour as a CSS
20
+ * value for its own surfaces, so the stored spelling is a value rather than a
21
+ * token reference; `agent-orb-appearance.test.ts` holds this table to the
22
+ * shipped design-system tokens, and the ESLint colour-literal exemption in the
23
+ * root config names this file.
24
+ */
25
+ export const agentOrbColorValues = {
26
+ cobalto: "#1e3cff",
27
+ verde: "#00a85a",
28
+ rosso: "#d3302f",
29
+ arancio: "#ff5a1f",
30
+ };
31
+ /** The design-system token each colour paints with. */
32
+ export const agentOrbColorTokens = {
33
+ cobalto: "var(--nc-identity-cobalto)",
34
+ verde: "var(--nc-identity-verde)",
35
+ rosso: "var(--nc-identity-rosso)",
36
+ arancio: "var(--nc-identity-arancio)",
37
+ };
38
+ /** The `shape` value a chosen form is stored as. */
39
+ export const shapeForOrb = (form) => `orb:${form}`;
40
+ /** The form an Agent wears until a person chooses one. */
41
+ export const defaultAgentOrbForm = "orbit";
42
+ export const defaultAgentOrbShape = shapeForOrb(defaultAgentOrbForm);
43
+ /**
44
+ * The retired Forma spellings, each carried to one orb so an Agent that chose
45
+ * a form before the orbs keeps a distinct look until someone changes it. The
46
+ * five role names are the launch Agents' own stored spellings.
47
+ */
48
+ const retiredShapes = new Map([
49
+ ["forma:uovo", "orbit"],
50
+ ["forma:pillola", "ribbon"],
51
+ ["forma:esagono", "lattice"],
52
+ ["forma:quadrato", "globe"],
53
+ ["forma:nuvola", "network"],
54
+ ["forma:goccia", "braid"],
55
+ ["forma:triangolo", "morph"],
56
+ ["forma:research", "orbit"],
57
+ ["forma:build", "braid"],
58
+ ["forma:design", "network"],
59
+ ["forma:operate", "globe"],
60
+ ["forma:review", "lattice"],
61
+ ]);
62
+ /**
63
+ * Resolve the stored strings to what `AgentOrb` draws. The root Agent is Nadia
64
+ * and wears her striped ribbon whatever is stored, since her look is fixed. An
65
+ * unknown shape is the default form; an unknown colour is undefined, which
66
+ * leaves the orb the inherited text colour.
67
+ */
68
+ export function resolveAgentOrb(stored, { root = false } = {}) {
69
+ if (root)
70
+ return { form: "nadia", color: undefined };
71
+ return { form: formFor(stored.shape), color: colorFor(stored.color) };
72
+ }
73
+ function formFor(shape) {
74
+ if (!shape)
75
+ return defaultAgentOrbForm;
76
+ // A Map, not an object: the shape is a stored string from a machine, and an
77
+ // object lookup would hand back `toString` or `constructor` for those spellings.
78
+ return (agentOrbForms.find((form) => shape === shapeForOrb(form)) ??
79
+ retiredShapes.get(shape) ??
80
+ defaultAgentOrbForm);
81
+ }
82
+ function colorFor(color) {
83
+ if (!color)
84
+ return undefined;
85
+ const stored = color.toLowerCase();
86
+ return agentOrbColors.find((name) => stored === agentOrbColorValues[name] || stored === name);
87
+ }
@@ -1,10 +1,12 @@
1
1
  import { type CSSProperties } from "react";
2
- import { type AgentOrbForm, type AgentOrbSize } from "../internal/agent-orb.js";
3
- export type { AgentOrbForm, AgentOrbSize } from "../internal/agent-orb.js";
2
+ import { type AgentOrbFigure, type AgentOrbSize } from "../internal/agent-orb.js";
3
+ export type { AgentOrbFigure, AgentOrbForm, AgentOrbSize } from "../internal/agent-orb.js";
4
4
  import type { AgentOrbState } from "../internal/agent-orb-motion.js";
5
5
  export type { AgentOrbState } from "../internal/agent-orb-motion.js";
6
6
  export type AgentOrbProps = {
7
- form: AgentOrbForm;
7
+ /** A chosen form, or `nadia`: her striped ribbon, whose colours are fixed. */
8
+ form: AgentOrbFigure;
9
+ /** The pigment of a chosen form; on Nadia, only her third, white stripe takes it. */
8
10
  color?: CSSProperties["color"];
9
11
  size?: AgentOrbSize;
10
12
  paused?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-orb.d.ts","sourceRoot":"","sources":["../../src/components/agent-orb.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAiB,MAAM,uBAAuB,CAAC;AAG5F,YAAY,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAExE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,MAAM,MAAM,aAAa,GAAG;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAElF,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,KAAK,EACL,IAAS,EACT,MAAc,EACd,KAAc,EACd,SAAa,EACb,UAAkB,EAClB,KAAK,EACL,SAAS,GACV,EAAE,aAAa,+BAyCf"}
1
+ {"version":3,"file":"agent-orb.d.ts","sourceRoot":"","sources":["../../src/components/agent-orb.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,aAAa,EAAqB,MAAM,OAAO,CAAC;AAE9D,OAAO,EAAE,KAAK,cAAc,EAAE,KAAK,YAAY,EAAiB,MAAM,uBAAuB,CAAC;AAG9F,YAAY,EAAE,cAAc,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAExF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAElE,MAAM,MAAM,aAAa,GAAG;IAC1B,8EAA8E;IAC9E,IAAI,EAAE,cAAc,CAAC;IACrB,qFAAqF;IACrF,KAAK,CAAC,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,GAAG,CAAC;IAAE,UAAU,EAAE,IAAI,CAAC;IAAC,KAAK,CAAC,EAAE,KAAK,CAAA;CAAE,GAAG;IAAE,UAAU,CAAC,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAElF,wBAAgB,QAAQ,CAAC,EACvB,IAAI,EACJ,KAAK,EACL,IAAS,EACT,MAAc,EACd,KAAc,EACd,SAAa,EACb,UAAkB,EAClB,KAAK,EACL,SAAS,GACV,EAAE,aAAa,+BAyCf"}
@@ -6,6 +6,7 @@ import { confirmDestructiveAction } from "./rules/confirm-destructive-action.js"
6
6
  import { iconControlAccessibleName } from "./rules/icon-control-accessible-name.js";
7
7
  import { noAppFeatureComponent } from "./rules/no-app-feature-component.js";
8
8
  import { noElevatedEdge } from "./rules/no-elevated-edge.js";
9
+ import { noExistenceOnlyAssertion } from "./rules/no-existence-only-assertion.js";
9
10
  import { noNestedOverlay } from "./rules/no-nested-overlay.js";
10
11
  import { noOffGridSpacing } from "./rules/no-off-grid-spacing.js";
11
12
  import { noRawShadow } from "./rules/no-raw-shadow.js";
@@ -17,6 +18,7 @@ import { noPackageComponentRedefinition } from "./rules/no-package-component-red
17
18
  import { noPairedSizeUtility } from "./rules/no-paired-size-utility.js";
18
19
  import { noRestingSettingsControl } from "./rules/no-resting-settings-control.js";
19
20
  import { noShadcnAppearanceOverride } from "./rules/no-shadcn-appearance-override.js";
21
+ import { noSubjectDerivedExpectation } from "./rules/no-subject-derived-expectation.js";
20
22
  import { noUnpinnedVercelFunctionsImport } from "./rules/no-unpinned-vercel-functions-import.js";
21
23
  import { noNativeVisibleControl } from "./rules/no-native-visible-control.js";
22
24
  import { noRepaintedFocus } from "./rules/no-repainted-focus.js";
@@ -84,6 +86,7 @@ const plugin = {
84
86
  "icon-control-accessible-name": iconControlAccessibleName,
85
87
  "no-app-feature-component": noAppFeatureComponent,
86
88
  "no-elevated-edge": noElevatedEdge,
89
+ "no-existence-only-assertion": noExistenceOnlyAssertion,
87
90
  "no-deprecated-zod-api": noDeprecatedZodApi,
88
91
  "no-mono-title-register": noMonoTitleRegister,
89
92
  "no-nested-overlay": noNestedOverlay,
@@ -95,6 +98,7 @@ const plugin = {
95
98
  "no-route-width-override": noRouteWidthOverride,
96
99
  "prefer-radio-for-few-static-options": preferRadioForFewStaticOptions,
97
100
  "no-shadcn-appearance-override": noShadcnAppearanceOverride,
101
+ "no-subject-derived-expectation": noSubjectDerivedExpectation,
98
102
  "no-native-visible-control": noNativeVisibleControl,
99
103
  "no-repainted-focus": noRepaintedFocus,
100
104
  "no-unpinned-vercel-functions-import": noUnpinnedVercelFunctionsImport,
@@ -0,0 +1,60 @@
1
+ /*
2
+ * One reader for `expect(subject).<modifiers>.<matcher>(args)`.
3
+ *
4
+ * Both assertion rules here judge a call by its matcher name, and the matcher
5
+ * is not the callee's property in the general case: `not`, `resolves` and
6
+ * `rejects` sit between `expect(...)` and the matcher, so the name has to be
7
+ * read by walking the member chain back to the `expect` call. This is that
8
+ * walk, kept in one place so the two rules cannot disagree about what counts
9
+ * as an assertion.
10
+ */
11
+
12
+ function propertyName(member) {
13
+ if (!member.computed && member.property.type === "Identifier") return member.property.name;
14
+ if (member.computed && member.property.type === "Literal") {
15
+ return typeof member.property.value === "string" ? member.property.value : undefined;
16
+ }
17
+ return undefined;
18
+ }
19
+
20
+ /** `a?.b` and `a?.()` arrive wrapped; the rules want the expression itself. */
21
+ function unwrapChain(node) {
22
+ return node?.type === "ChainExpression" ? node.expression : node;
23
+ }
24
+
25
+ function isExpectCall(node) {
26
+ if (node?.type !== "CallExpression") return false;
27
+ if (node.callee.type === "Identifier") return node.callee.name === "expect";
28
+ // `expect.soft(value)` and `expect.poll(fn)` open the same chain.
29
+ return node.callee.type === "MemberExpression" && node.callee.object.type === "Identifier"
30
+ ? node.callee.object.name === "expect"
31
+ : false;
32
+ }
33
+
34
+ /**
35
+ * Reads an assertion call. Returns `{ matcher, modifiers, subject, args }` for
36
+ * `expect(subject).<modifiers>.<matcher>(args)`, or `undefined` when the call
37
+ * is not an `expect` chain. `subject` is unwrapped of its `ChainExpression`.
38
+ */
39
+ export function expectAssertion(node) {
40
+ if (node.type !== "CallExpression" || node.callee.type !== "MemberExpression") return undefined;
41
+ const matcher = propertyName(node.callee);
42
+ if (!matcher) return undefined;
43
+
44
+ const modifiers = [];
45
+ let current = node.callee.object;
46
+ while (current.type === "MemberExpression") {
47
+ const name = propertyName(current);
48
+ if (!name) return undefined;
49
+ modifiers.unshift(name);
50
+ current = current.object;
51
+ }
52
+ if (!isExpectCall(current)) return undefined;
53
+
54
+ return {
55
+ matcher,
56
+ modifiers,
57
+ subject: unwrapChain(current.arguments[0]),
58
+ args: node.arguments,
59
+ };
60
+ }
@@ -0,0 +1,89 @@
1
+ /*
2
+ * An assertion that something exists is not an assertion about behavior.
3
+ *
4
+ * Two shapes in this suite pass for nearly any implementation.
5
+ *
6
+ * A `&&` or `||` subject returns one of its operands, not a boolean, so the
7
+ * short-circuit alone can satisfy the matcher: the left operand decides the
8
+ * result and the right one is never evaluated.
9
+ *
10
+ * An optional call yields `undefined` when the method is gone. That is exactly
11
+ * what `toBeFalsy()` and `toBeUndefined()` accept, which is how
12
+ * `expect(options.clientPrivileges?.()).toBeFalsy()` in
13
+ * `apps/portal/src/lib/server/nadia-oauth-provider.test.ts` kept asserting that
14
+ * OAuth grants no configuration privileges after the privilege hooks had been
15
+ * deleted from the provider.
16
+ *
17
+ * What is deliberately left alone: `toBeDefined()` / `toBeTruthy()` on any
18
+ * other subject. On a plain identifier or member read it is the type-narrowing
19
+ * step before reading a property off it. On a Testing Library `getBy*` query it
20
+ * is redundant, since the query already throws, but it is the suite's idiom for
21
+ * "this element renders" and replacing it mechanically produces
22
+ * `expect(getByText("X").textContent).toBe("X")`, which is a tautology of its
23
+ * own: 214 such sites were rewritten that way once and reverted. `toBeNull()`
24
+ * and `toBeUndefined()` on any other subject are a real claim of absence.
25
+ *
26
+ * Standard tools checked first: `vitest/expect-expect` refuses a test with no
27
+ * assertion at all and `vitest/valid-expect` checks the call's arity; neither
28
+ * asks what the asserted expression can produce, which is the whole question
29
+ * here.
30
+ */
31
+ import { expectAssertion } from "./expect-chain.js";
32
+
33
+ const SHORT_CIRCUIT_MATCHERS = new Set(["toBeDefined", "toBeTruthy", "toBeFalsy", "toBeUndefined"]);
34
+ const MISSING_VALUE_MATCHERS = new Set(["toBeFalsy", "toBeUndefined"]);
35
+
36
+ function isOptionalCall(node) {
37
+ return node?.type === "CallExpression" && node.optional === true;
38
+ }
39
+
40
+ export const noExistenceOnlyAssertion = {
41
+ meta: {
42
+ type: "problem",
43
+ docs: {
44
+ description:
45
+ "Disallow expectations that only assert a value exists, which pass for almost any implementation",
46
+ },
47
+ schema: [],
48
+ messages: {
49
+ shortCircuitSubject:
50
+ "`{{operator}}` returns one of its operands rather than a boolean, so `{{assertion}}` passes on the short-circuit alone, with the right operand never evaluated. Assert each operand separately, or assert the value the expression stands for.",
51
+ optionalCallAbsence:
52
+ "`{{assertion}}` on an optional call passes when the call never happens: a deleted or renamed method makes `?.()` yield `undefined`, which is both falsy and undefined. Assert the method is present, then assert what the call returns.",
53
+ },
54
+ },
55
+
56
+ create(context) {
57
+ function assertionText(matcher, modifiers) {
58
+ return [...modifiers, `${matcher}()`].join(".");
59
+ }
60
+
61
+ return {
62
+ CallExpression(node) {
63
+ const assertion = expectAssertion(node);
64
+ if (!assertion) return;
65
+ const { matcher, modifiers, subject } = assertion;
66
+ if (!subject) return;
67
+
68
+ if (modifiers.length > 0) return;
69
+
70
+ if (subject.type === "LogicalExpression" && SHORT_CIRCUIT_MATCHERS.has(matcher)) {
71
+ context.report({
72
+ node,
73
+ messageId: "shortCircuitSubject",
74
+ data: { operator: subject.operator, assertion: assertionText(matcher, modifiers) },
75
+ });
76
+ return;
77
+ }
78
+
79
+ if (isOptionalCall(subject) && MISSING_VALUE_MATCHERS.has(matcher)) {
80
+ context.report({
81
+ node,
82
+ messageId: "optionalCallAbsence",
83
+ data: { assertion: assertionText(matcher, modifiers) },
84
+ });
85
+ }
86
+ },
87
+ };
88
+ },
89
+ };
@@ -0,0 +1,129 @@
1
+ /*
2
+ * The expected value cannot come out of the module under test.
3
+ *
4
+ * When a test compares a result with a constant it imported from the sibling
5
+ * module it is testing, both sides move together: editing the implementation
6
+ * edits the expectation, and no change to the value can ever turn the test
7
+ * red. What looks like a pinned contract is a restatement of the code.
8
+ *
9
+ * Three of these were live here. `viewer-time-zone.test.ts` asserted
10
+ * `expect(await viewerTimeZone()).toBe(DEFAULT_VIEWER_TIME_ZONE)` with
11
+ * `DEFAULT_VIEWER_TIME_ZONE` imported from `./viewer-time-zone`, so the
12
+ * default the Portal falls back to was never pinned to a time zone at all.
13
+ * `contract-v1.test.ts` asserted the served ETag header against
14
+ * `openApiV1Etag` from `./contract-v1`, so the published API contract's
15
+ * validator was compared with itself, and a token TTL went the same way.
16
+ *
17
+ * The fix is to write the literal (`"Europe/Rome"`), or to compute the
18
+ * expectation from something the subject does not own — a checked-in fixture,
19
+ * a hash taken in the test, the wire format itself.
20
+ *
21
+ * Scope: only a relative import (`./`, `../`) is the subject. A constant from
22
+ * a package or an alias (`@/…`, `vitest`) is a third party's contract, and a
23
+ * value the test file declares itself is a value the test chose. Only a bare
24
+ * identifier or a member read off one counts: a call, a template literal or a
25
+ * literal built around the identifier is a transformation the test performs,
26
+ * and the transformation is the thing being asserted.
27
+ *
28
+ * A relative import that resolves into a `tests/` or `fixtures/` directory is
29
+ * the harness, not the subject: `PORTAL_TEST_ADMIN.userId` from
30
+ * `apps/portal/tests/support/postgres-testdb` is the row the test seeded, so
31
+ * comparing a query result with it asserts that the code returned what was put
32
+ * in. Those are excluded by the resolved path, not by the specifier, because a
33
+ * test under `tests/` reaches its own support modules as `../support/…`.
34
+ */
35
+ import { dirname, resolve, sep } from "node:path";
36
+
37
+ import { expectAssertion } from "./expect-chain.js";
38
+
39
+ const EQUALITY_MATCHERS = new Set(["toBe", "toEqual", "toStrictEqual"]);
40
+ const TEST_FILE = /\.test\.tsx?$/;
41
+ const HARNESS_DIRECTORIES = new Set(["tests", "test", "__tests__", "fixtures", "__fixtures__"]);
42
+
43
+ /** True when the import lands in a directory the test side owns. */
44
+ function isHarnessModule(filename, source) {
45
+ const resolved = resolve(dirname(filename), source);
46
+ const segments = resolved.split(sep);
47
+ if (segments.some((segment) => HARNESS_DIRECTORIES.has(segment))) return true;
48
+ // `openrouter-wire.fixture.ts` beside its subject: a fixture by name, not by directory.
49
+ return /\.fixture(\.[cm]?[jt]sx?)?$/.test(segments[segments.length - 1]);
50
+ }
51
+
52
+ /** The identifier a bare read or a member read (`contract.etag`) starts from. */
53
+ function rootIdentifier(node) {
54
+ let current = node;
55
+ while (current?.type === "MemberExpression" || current?.type === "ChainExpression") {
56
+ current = current.type === "ChainExpression" ? current.expression : current.object;
57
+ }
58
+ return current?.type === "Identifier" ? current : undefined;
59
+ }
60
+
61
+ export const noSubjectDerivedExpectation = {
62
+ meta: {
63
+ type: "problem",
64
+ docs: {
65
+ description: "Disallow an expected value imported from the sibling module the test covers",
66
+ },
67
+ schema: [],
68
+ messages: {
69
+ subjectDerived:
70
+ "`{{name}}` comes from `{{source}}`, the module this test covers, so the expectation moves with the implementation and no change to that value can fail this test. Write the expected value as a literal here, or compute it from something the subject does not own.",
71
+ },
72
+ },
73
+
74
+ create(context) {
75
+ if (!TEST_FILE.test(context.filename)) return {};
76
+
77
+ const sourceCode = context.sourceCode;
78
+ /** @type {Map<import('eslint').Scope.Variable, string>} */
79
+ const subjectBindings = new Map();
80
+
81
+ function bindingSource(identifier) {
82
+ for (let scope = sourceCode.getScope(identifier); scope; scope = scope.upper) {
83
+ const variable = scope.set.get(identifier.name);
84
+ // The nearest binding wins: a local declaration shadowing the import
85
+ // is a value this test chose, so it drops out here.
86
+ if (variable) return subjectBindings.get(variable);
87
+ }
88
+ return undefined;
89
+ }
90
+
91
+ return {
92
+ Program(node) {
93
+ for (const statement of node.body) {
94
+ if (statement.type !== "ImportDeclaration") continue;
95
+ if (statement.importKind === "type") continue;
96
+ const source = statement.source.value;
97
+ if (typeof source !== "string") continue;
98
+ if (!source.startsWith("./") && !source.startsWith("../")) continue;
99
+ if (isHarnessModule(context.filename, source)) continue;
100
+ for (const specifier of statement.specifiers) {
101
+ if (specifier.importKind === "type") continue;
102
+ for (const variable of sourceCode.getDeclaredVariables(specifier)) {
103
+ subjectBindings.set(variable, source);
104
+ }
105
+ }
106
+ }
107
+ },
108
+
109
+ CallExpression(node) {
110
+ const assertion = expectAssertion(node);
111
+ if (!assertion || !EQUALITY_MATCHERS.has(assertion.matcher)) return;
112
+ const [expected] = assertion.args;
113
+ if (!expected) return;
114
+ if (expected.type !== "Identifier" && expected.type !== "MemberExpression") return;
115
+
116
+ const root = rootIdentifier(expected);
117
+ if (!root) return;
118
+ const source = bindingSource(root);
119
+ if (!source) return;
120
+
121
+ context.report({
122
+ node: expected,
123
+ messageId: "subjectDerived",
124
+ data: { name: sourceCode.getText(expected), source },
125
+ });
126
+ },
127
+ };
128
+ },
129
+ };
package/dist/index.d.ts CHANGED
@@ -7,7 +7,6 @@ export * from "./components/activity-orb.js";
7
7
  export * from "./components/activity-calendar.js";
8
8
  export * from "./components/agent-card.js";
9
9
  export * from "./components/agent-orb.js";
10
- export * from "./components/agent-visual.js";
11
10
  export * from "./components/alert.js";
12
11
  export * from "./components/alert-dialog.js";
13
12
  export * from "./components/aspect-ratio.js";
@@ -68,7 +67,6 @@ export * from "./components/field.js";
68
67
  export * from "./components/file-type-icon.js";
69
68
  export * from "./components/footer.js";
70
69
  export * from "./components/form.js";
71
- export * from "./components/forma-avatar.js";
72
70
  export * from "./components/glass-control.js";
73
71
  export * from "./components/glyph-spinner.js";
74
72
  export * from "./components/handoff-banner.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -7,7 +7,6 @@ export * from "./components/activity-orb.js";
7
7
  export * from "./components/activity-calendar.js";
8
8
  export * from "./components/agent-card.js";
9
9
  export * from "./components/agent-orb.js";
10
- export * from "./components/agent-visual.js";
11
10
  export * from "./components/alert.js";
12
11
  export * from "./components/alert-dialog.js";
13
12
  export * from "./components/aspect-ratio.js";
@@ -68,7 +67,6 @@ export * from "./components/field.js";
68
67
  export * from "./components/file-type-icon.js";
69
68
  export * from "./components/footer.js";
70
69
  export * from "./components/form.js";
71
- export * from "./components/forma-avatar.js";
72
70
  export * from "./components/glass-control.js";
73
71
  export * from "./components/glyph-spinner.js";
74
72
  export * from "./components/handoff-banner.js";
@@ -1,7 +1,7 @@
1
- import type { AgentOrbForm } from "./agent-orb.js";
1
+ import type { AgentOrbFigure } from "./agent-orb.js";
2
2
  export type AgentOrbState = "idle" | "working" | "waiting" | "blocked" | "thinking" | "done";
3
- export declare function stateSpeed(state: AgentOrbState, form: AgentOrbForm): number;
4
- export declare function motionPose(form: AgentOrbForm, state: AgentOrbState, age: number): {
3
+ export declare function stateSpeed(state: AgentOrbState, form: AgentOrbFigure): number;
4
+ export declare function motionPose(form: AgentOrbFigure, state: AgentOrbState, age: number): {
5
5
  sx: number;
6
6
  sy: number;
7
7
  x: number;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-orb-motion.d.ts","sourceRoot":"","sources":["../../src/internal/agent-orb-motion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAY7F,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,YAAY,UAGlE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM;;;;;;;;EA0C/E;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,wBAAwB,EACjC,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,KAAK,EAAE,MAAM,GAAG,OAAO,QAgExB"}
1
+ {"version":3,"file":"agent-orb-motion.d.ts","sourceRoot":"","sources":["../../src/internal/agent-orb-motion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;AAa7F,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,EAAE,IAAI,EAAE,cAAc,UAGpE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,EAAE,MAAM;;;;;;;;EA0CjF;AAED,wBAAgB,WAAW,CACzB,OAAO,EAAE,wBAAwB,EACjC,KAAK,EAAE,aAAa,EACpB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,KAAK,EAAE,MAAM,GAAG,OAAO,QAgExB"}
@@ -1,5 +1,6 @@
1
1
  const personalities = {
2
2
  ribbon: { speed: 0.55, tilt: 0.15 },
3
+ nadia: { speed: 0.55, tilt: 0.15 },
3
4
  lattice: { speed: 0.28, tilt: -0.12 },
4
5
  orbit: { speed: 0.7, tilt: 0.2 },
5
6
  globe: { speed: 0.35, tilt: -0.16 },
@@ -1,3 +1,4 @@
1
+ import { type ModeOpts, type OrbFrame } from "thinking-orbs/engine";
1
2
  import { type AgentOrbState } from "./agent-orb-motion.js";
2
3
  declare const agentOrbForms: {
3
4
  readonly orbit: "working";
@@ -9,8 +10,17 @@ declare const agentOrbForms: {
9
10
  readonly morph: "shaping";
10
11
  };
11
12
  export type AgentOrbForm = keyof typeof agentOrbForms;
13
+ /** A chosen form, or Nadia's own ribbon, which nobody chooses. */
14
+ export type AgentOrbFigure = AgentOrbForm | "nadia";
12
15
  export type AgentOrbSize = 20 | 24 | 32 | 40 | 64 | 96 | 160;
13
- export declare function mountAgentOrb(canvas: HTMLCanvasElement, form: AgentOrbForm, size: AgentOrbSize, initiallyPaused: boolean, initialState?: AgentOrbState): {
16
+ /**
17
+ * The vendor's `ribbon` geometry (thinking-orbs 0.3.1, MIT, Jakub Antalik),
18
+ * repeated here only so every dot carries its lane through the depth sort. The
19
+ * engine sorts dots by depth each frame, so colouring by array position makes
20
+ * a stripe jump between lanes as the ribbon turns; the lane is the stable id.
21
+ */
22
+ export declare function stripedRibbonFrame(size: number, t: number, opts: ModeOpts): OrbFrame;
23
+ export declare function mountAgentOrb(canvas: HTMLCanvasElement, form: AgentOrbFigure, size: AgentOrbSize, initiallyPaused: boolean, initialState?: AgentOrbState): {
14
24
  destroy(): void;
15
25
  setPaused(_value: boolean): void;
16
26
  setState(_value: AgentOrbState): void;