@ekanos/harness 0.1.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 (72) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +879 -0
  3. package/dist/app.d.ts +4 -0
  4. package/dist/app.js +11 -0
  5. package/dist/config.d.ts +18 -0
  6. package/dist/config.js +34 -0
  7. package/dist/internal/components/ask-assistant-bridge.d.ts +10 -0
  8. package/dist/internal/components/ask-assistant-bridge.js +50 -0
  9. package/dist/internal/components/dashboard-grid.d.ts +45 -0
  10. package/dist/internal/components/dashboard-grid.js +84 -0
  11. package/dist/internal/components/dev-toolbar.d.ts +15 -0
  12. package/dist/internal/components/dev-toolbar.js +155 -0
  13. package/dist/internal/components/harness-providers.d.ts +16 -0
  14. package/dist/internal/components/harness-providers.js +83 -0
  15. package/dist/internal/components/harness-widget-provider.d.ts +60 -0
  16. package/dist/internal/components/harness-widget-provider.js +84 -0
  17. package/dist/internal/components/i18n-provider.d.ts +9 -0
  18. package/dist/internal/components/i18n-provider.js +9 -0
  19. package/dist/internal/components/row-groups.d.ts +23 -0
  20. package/dist/internal/components/row-groups.js +37 -0
  21. package/dist/internal/components/surface-nav.d.ts +4 -0
  22. package/dist/internal/components/surface-nav.js +70 -0
  23. package/dist/internal/components/viewport-frame.d.ts +14 -0
  24. package/dist/internal/components/viewport-frame.js +27 -0
  25. package/dist/internal/components/widget-boundary.d.ts +25 -0
  26. package/dist/internal/components/widget-boundary.js +44 -0
  27. package/dist/internal/components/widget-surface.d.ts +20 -0
  28. package/dist/internal/components/widget-surface.js +76 -0
  29. package/dist/internal/lib/fonts.d.ts +2 -0
  30. package/dist/internal/lib/fonts.js +22 -0
  31. package/dist/internal/lib/harness-fetch-interceptor.d.ts +89 -0
  32. package/dist/internal/lib/harness-fetch-interceptor.js +101 -0
  33. package/dist/internal/lib/harness-live-fetch.d.ts +66 -0
  34. package/dist/internal/lib/harness-live-fetch.js +121 -0
  35. package/dist/internal/lib/harness-query-client.d.ts +43 -0
  36. package/dist/internal/lib/harness-query-client.js +103 -0
  37. package/dist/internal/lib/http-fixtures.d.ts +145 -0
  38. package/dist/internal/lib/http-fixtures.js +256 -0
  39. package/dist/internal/lib/i18n.d.ts +2 -0
  40. package/dist/internal/lib/i18n.js +17 -0
  41. package/dist/internal/lib/redact.d.ts +33 -0
  42. package/dist/internal/lib/redact.js +43 -0
  43. package/dist/internal/lib/toolbar-context.d.ts +59 -0
  44. package/dist/internal/lib/toolbar-context.js +124 -0
  45. package/dist/internal/registry-context.d.ts +27 -0
  46. package/dist/internal/registry-context.js +51 -0
  47. package/dist/internal/routes/activation-page.d.ts +33 -0
  48. package/dist/internal/routes/activation-page.js +242 -0
  49. package/dist/internal/routes/index-page.d.ts +13 -0
  50. package/dist/internal/routes/index-page.js +62 -0
  51. package/dist/internal/routes/integration-layout.d.ts +9 -0
  52. package/dist/internal/routes/integration-layout.js +84 -0
  53. package/dist/internal/routes/root-layout.d.ts +34 -0
  54. package/dist/internal/routes/root-layout.js +39 -0
  55. package/dist/internal/routes/single-widget-page.d.ts +6 -0
  56. package/dist/internal/routes/single-widget-page.js +30 -0
  57. package/dist/internal/routes/tile-page.d.ts +1 -0
  58. package/dist/internal/routes/tile-page.js +88 -0
  59. package/dist/internal/routes/triggers-page.d.ts +1 -0
  60. package/dist/internal/routes/triggers-page.js +386 -0
  61. package/dist/internal/routes/widgets-page.d.ts +1 -0
  62. package/dist/internal/routes/widgets-page.js +19 -0
  63. package/dist/internal/surfaces.d.ts +25 -0
  64. package/dist/internal/surfaces.js +29 -0
  65. package/dist/mocks/team-account-workspace.d.ts +78 -0
  66. package/dist/mocks/team-account-workspace.js +86 -0
  67. package/dist/registry.d.ts +418 -0
  68. package/dist/registry.js +82 -0
  69. package/dist/routes.d.ts +24 -0
  70. package/dist/routes.js +15 -0
  71. package/dist/styles.css +236 -0
  72. package/package.json +101 -0
@@ -0,0 +1,236 @@
1
+ /*
2
+ * styles.src.css — the harness's own Tailwind layer.
3
+ *
4
+ * Built into `dist/styles.css` by this package's CSS build, which is what
5
+ * `@ekanos/harness/styles.css` resolves to. The shell imports that ONE line and
6
+ * adds only the `@source` globs covering code the shell owns — its own
7
+ * integrations and anything they import. Everything the HARNESS renders is
8
+ * accounted for here, so a partner never has to know which globs the chrome
9
+ * needs.
10
+ *
11
+ * The `@source` block below is injected at build time rather than written
12
+ * here, because the two layouts this file has to serve resolve from different
13
+ * directories: in the monorepo consumers compile `src/`, and a partner's
14
+ * install compiles `dist/`.
15
+ */
16
+
17
+ @import 'tailwindcss';
18
+
19
+ /* Fusion design tokens + the Tailwind v4 @theme preset, consumed from the
20
+ * published package instead of a hand-maintained fork. This single import
21
+ * pulls in the token values, the base rules, the `dark:` variant and the full
22
+ * semantic @theme map. Resolves to `@ekanos/ui/dist/theme.css`, so that
23
+ * package must be built first. */
24
+ @import '@ekanos/ui/theme.css';
25
+ @import 'tw-animate-css';
26
+
27
+ /* ── Tailwind content sources ───────────────────────────────────────────────
28
+ *
29
+ * INJECTED BY this package's CSS build — edit that, not this file.
30
+ *
31
+ * Each package is spelled twice on purpose: once for the monorepo layout and
32
+ * once for an installed tarball. A glob that matches nothing is a silent
33
+ * no-op; the missing half renders everything unstyled with no error. */
34
+ @source '../src/**/*.{ts,tsx}'; /* this package, as workspace consumers compile it */
35
+ @source './**/*.js'; /* this package, as a partner installs it */
36
+ @source '../../ekanos-ui/src/**/*.{ts,tsx}'; /* @ekanos/ui, in the monorepo */
37
+ @source '../../integration-sdk/src/**/*.{ts,tsx}'; /* @ekanos/sdk, in the monorepo */
38
+ @source '../../ui/dist/**/*.js'; /* @ekanos/ui, installed and hoisted */
39
+ @source '../../sdk/dist/**/*.js'; /* @ekanos/sdk, installed and hoisted */
40
+ @source '../node_modules/@ekanos/ui/dist/**/*.js'; /* @ekanos/ui, installed nested */
41
+ @source '../node_modules/@ekanos/sdk/dist/**/*.js'; /* @ekanos/sdk, installed nested */
42
+
43
+
44
+ /* The `dark:` variant now comes from `@ekanos/ui/theme.css` (a richer
45
+ * `@custom-variant dark` covering `.dark`, `[data-theme='dark']` and the
46
+ * system-preference media query, all of which next-themes' `.light`/`.dark`
47
+ * class opts in and out of). No local re-declaration needed. */
48
+
49
+ /* `src/internal/lib/fonts.ts` self-hosts Figtree + Bricolage Grotesque through
50
+ * next/font and exposes them on `--font-sans-fallback` /
51
+ * `--font-heading-fallback`; `RootLayout` puts those classes on `<html>`.
52
+ * `@ekanos/ui/tokens.css` ships a literal `'Figtree'` stack (it cannot know
53
+ * this package's next/font variable names); remap onto the bundled webfonts so
54
+ * the harness renders the self-hosted faces rather than the system fallback.
55
+ * Unlayered, so it wins over the `layer(base)` token import above. */
56
+ :root {
57
+ --font-sans:
58
+ var(--font-sans-fallback), -apple-system, BlinkMacSystemFont, system-ui,
59
+ sans-serif;
60
+ --font-heading: var(--font-heading-fallback), var(--font-sans);
61
+ }
62
+
63
+ /* ── Font Awesome Free: missing-glyph fallback ────────────────────────────────
64
+ *
65
+ * The harness loads Font Awesome FREE (see the note in `RootLayout` — the
66
+ * host's Pro webfonts are commercially licensed and cannot ship in a partner's
67
+ * scaffold). Free has a smaller glyph set, so a Pro-only name such as
68
+ * `fa-aperture` or `fa-clouds-sun` never gets a `--fa` codepoint, `content:
69
+ * var(--fa)` computes to nothing, and the icon vanishes with no warning.
70
+ *
71
+ * `:where()` carries zero specificity, so every real `.fa-<name>{--fa:…}` rule
72
+ * in the vendor CSS still wins. Only genuinely-absent glyphs fall through to
73
+ * the question mark, which reads as "this glyph isn't in the free font" rather
74
+ * than as a broken layout. */
75
+ :where(.fa-solid, .fa-regular, .fa-light, .fa-duotone, .fa-thin, .fa) {
76
+ --fa: '\f059'; /* fa-circle-question */
77
+ }
78
+
79
+ /* ── Font Awesome Free: hand `::before` back to the icon font ─────────────────
80
+ *
81
+ * `@ekanos/ui/theme.css` imports that package's `base.css`, which paints a
82
+ * placeholder for a host that loads NO icon font at all:
83
+ *
84
+ * :where(i[class*='fa-'])::before {
85
+ * content: var(--ekanos-icon-fallback, '\25a1');
86
+ * font-family: ui-sans-serif, system-ui, sans-serif;
87
+ * font-weight: 400;
88
+ * opacity: 0.55;
89
+ * }
90
+ *
91
+ * Its comment reasons that `:where()` at (0,0,1) loses to Font Awesome's own
92
+ * `:is(…)::before` at (0,1,1), so the rule "contributes nothing" the instant a
93
+ * Font Awesome stylesheet is present. That holds for `content` — the ONLY
94
+ * property FA declares on the pseudo-element (`content: var(--fa)/""`). It does
95
+ * NOT hold for `font-family`, `font-weight` or `opacity`: FA declares the family
96
+ * and weight on the ELEMENT (`.fa-solid { font-family: var(--_fa-family);
97
+ * font-weight: var(--fa-style, 900) }`), never on `::before`. So those three
98
+ * declarations have no competitor at pseudo-element level and win regardless of
99
+ * specificity or cascade layer — a declared value always beats an inherited one.
100
+ *
101
+ * Net effect with FA Free loaded: `content` is the CORRECT codepoint (verified
102
+ * in-browser: U+F043 for `fa-droplet`) but it is painted in `system-ui` at
103
+ * weight 400, and no system font carries a Private Use Area glyph there — so
104
+ * every icon rendered as the browser's missing-glyph hex box (a rectangle of
105
+ * tiny hex digits, which reads as a striped/barcode box) at 55% opacity.
106
+ *
107
+ * Handing the pseudo-element back to `inherit` restores FA's own family and
108
+ * weight off the `<i>`, which is correct for solid, regular AND brands without
109
+ * naming a family here. `:is()` at (0,1,0) beats the `:where()` it undoes, and
110
+ * being unlayered it also outranks the `layer(base)` base.css is imported into.
111
+ *
112
+ * This lives here, not in `@ekanos/ui`, because the placeholder is correct for
113
+ * the case it was written for — a host with no icon font. It is only wrong for a
114
+ * host that HAS one, which is this app. */
115
+ :is(
116
+ .fa,
117
+ .fas,
118
+ .far,
119
+ .fab,
120
+ .fa-solid,
121
+ .fa-regular,
122
+ .fa-brands,
123
+ .fa-classic,
124
+ .fa-light,
125
+ .fa-thin,
126
+ .fa-duotone,
127
+ .fa-sharp
128
+ )::before {
129
+ font-family: inherit;
130
+ font-weight: inherit;
131
+ opacity: 1;
132
+ }
133
+
134
+ /* The Pro-only FAMILY classes get no `font-family` from the Free stylesheet at
135
+ * all, so an `<i class="fa-light fa-spinner">` falls back to the body font and
136
+ * renders tofu even when `--fa` holds a codepoint Free ships.
137
+ * `IconStyleOverrideProvider value="solid"` in `RootLayout` rewrites these
138
+ * for everything under it, but it cannot reach an icon rendered outside that
139
+ * subtree — and `@ekanos/ui`'s own `Spinner` hardcodes `fa-light fa-spinner`. Pin
140
+ * them to the one weight Free actually ships so they degrade to the solid face
141
+ * instead of vanishing. Family is spelled out because `--_fa-family` is set by
142
+ * the very FA rule that does not match these classes. */
143
+ i:is(.fa-light, .fa-thin, .fa-duotone, .fa-sharp) {
144
+ font-family: 'Font Awesome 7 Free';
145
+ font-weight: 900;
146
+ }
147
+
148
+ /* ── Pro glyph names → their nearest Free equivalent ──────────────────────────
149
+ *
150
+ * The `--fa: '\f059'` rule above is the LAST resort: it keeps an unknown glyph
151
+ * legible as a question mark instead of collapsing the layout. But a wall of
152
+ * question marks is its own kind of broken-looking, and several of the Pro
153
+ * names our example integrations use have an obvious Free counterpart that
154
+ * carries the same meaning.
155
+ *
156
+ * Aliasing those by codepoint here is strictly better than the question mark
157
+ * and strictly honest about what it is: an APPROXIMATION for the Free build.
158
+ * `fa-cloud` is not `fa-clouds` and `fa-sun` is not `fa-sun-bright` — in
159
+ * production, where Fusion loads Font Awesome Pro, the real Pro rule is (0,1,0)
160
+ * and lives in an unlayered vendor sheet, so it wins over the `i.fa-*` selectors
161
+ * below and the authored Pro glyph renders exactly as designed. Nothing here
162
+ * changes what a correctly licensed host shows.
163
+ *
164
+ * Keep this list to names our own bundled examples actually reference; it is a
165
+ * courtesy for the demo surfaces, not a general-purpose Pro→Free shim. */
166
+ i.fa-sun-bright {
167
+ --fa: '\f185'; /* fa-sun */
168
+ }
169
+ i.fa-moon-stars {
170
+ --fa: '\f186'; /* fa-moon */
171
+ }
172
+ i.fa-cloud-fog {
173
+ --fa: '\f75f'; /* fa-smog */
174
+ }
175
+ i.fa-cloud-drizzle {
176
+ --fa: '\f73d'; /* fa-cloud-rain */
177
+ }
178
+ i.fa-cloud-showers {
179
+ --fa: '\f740'; /* fa-cloud-showers-heavy */
180
+ }
181
+ i.fa-clouds {
182
+ --fa: '\f0c2'; /* fa-cloud */
183
+ }
184
+ i.fa-clouds-sun {
185
+ --fa: '\f6c4'; /* fa-cloud-sun */
186
+ }
187
+ i.fa-clouds-moon {
188
+ --fa: '\f6c3'; /* fa-cloud-moon */
189
+ }
190
+ i.fa-cloud-question {
191
+ --fa: '\f059'; /* fa-circle-question */
192
+ }
193
+ i.fa-sparkles {
194
+ --fa: '\e2ca'; /* fa-wand-magic-sparkles */
195
+ }
196
+
197
+ @layer base {
198
+ body {
199
+ @apply bg-background text-foreground;
200
+ font-feature-settings:
201
+ 'rlig' 1,
202
+ 'calt' 1;
203
+ }
204
+
205
+ *,
206
+ ::after,
207
+ ::before,
208
+ ::backdrop,
209
+ ::file-selector-button {
210
+ border-color: var(--border, currentColor);
211
+ }
212
+
213
+ input::placeholder,
214
+ textarea::placeholder {
215
+ color: theme(--color-muted-foreground);
216
+ }
217
+
218
+ @media (prefers-reduced-motion: reduce) {
219
+ *,
220
+ ::before,
221
+ ::after {
222
+ animation-duration: 0.01ms !important;
223
+ animation-iteration-count: 1 !important;
224
+ transition-duration: 0.01ms !important;
225
+ }
226
+ }
227
+ }
228
+
229
+ /*
230
+ * Mirrors the host: shadcn inputs default to a transparent fill, which
231
+ * disappears against the off-white page background.
232
+ */
233
+ input.border-input,
234
+ textarea.border-input {
235
+ background-color: var(--card);
236
+ }
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "@ekanos/harness",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "The Ekanos integration dev harness — every surface of an integration rendered in real Fusion chrome, from fixtures, with no Supabase, auth or network.",
6
+ "license": "MIT",
7
+ "bugs": {
8
+ "email": "npm@govastly.com"
9
+ },
10
+ "sideEffects": [
11
+ "**/*.css",
12
+ "**/i18n.js"
13
+ ],
14
+ "ekanos": {
15
+ "shellContract": 1
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "exports": {
23
+ "./registry": {
24
+ "types": "./dist/registry.d.ts",
25
+ "default": "./dist/registry.js"
26
+ },
27
+ "./config": {
28
+ "types": "./dist/config.d.ts",
29
+ "default": "./dist/config.js"
30
+ },
31
+ "./app": {
32
+ "types": "./dist/app.d.ts",
33
+ "default": "./dist/app.js"
34
+ },
35
+ "./routes": {
36
+ "types": "./dist/routes.d.ts",
37
+ "default": "./dist/routes.js"
38
+ },
39
+ "./styles.css": "./dist/styles.css",
40
+ "./mocks/team-account-workspace": {
41
+ "types": "./dist/mocks/team-account-workspace.d.ts",
42
+ "default": "./dist/mocks/team-account-workspace.js"
43
+ }
44
+ },
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "dependencies": {
49
+ "@fortawesome/fontawesome-free": "^7.3.1",
50
+ "i18next": "25.10.10",
51
+ "next-themes": "0.4.6",
52
+ "react-i18next": "^16.6.6",
53
+ "tw-animate-css": "1.4.0"
54
+ },
55
+ "peerDependencies": {
56
+ "@ekanos/sdk": "^0.1.2",
57
+ "@ekanos/ui": "^0.1.2",
58
+ "@tanstack/react-query": "^5.101.4",
59
+ "next": "^16.0.0",
60
+ "react": "^19.2.8",
61
+ "react-dom": "^19.2.8",
62
+ "tailwindcss": "^4.0.0"
63
+ },
64
+ "devDependencies": {
65
+ "@ekanos/sdk": "0.1.2",
66
+ "@ekanos/ui": "0.1.2",
67
+ "@kit/eslint-config": "0.2.0",
68
+ "@kit/prettier-config": "0.1.0",
69
+ "@kit/tsconfig": "0.1.0",
70
+ "@tailwindcss/postcss": "4.3.3",
71
+ "@tanstack/react-query": "5.102.8",
72
+ "@types/node": "25.0.1",
73
+ "@types/react": "19.2.18",
74
+ "@types/react-dom": "19.2.5",
75
+ "next": "16.3.3",
76
+ "react": "19.2.8",
77
+ "react-dom": "19.2.8",
78
+ "tailwindcss": "4.3.3",
79
+ "tsup": "8.5.1",
80
+ "typescript": "^5.9.3",
81
+ "vitest": "4.1.10",
82
+ "zod": "3.25.76"
83
+ },
84
+ "prettier": "@kit/prettier-config",
85
+ "typesVersions": {
86
+ "*": {
87
+ "*": [
88
+ "dist/*"
89
+ ]
90
+ }
91
+ },
92
+ "scripts": {
93
+ "build": "tsup && tsc -p tsconfig.build.json && node scripts/rewrite-esm-specifiers.mjs && node scripts/build-css.mjs",
94
+ "clean": "git clean -xdf .turbo node_modules dist",
95
+ "format": "prettier --check \"**/*.{ts,tsx,mjs}\"",
96
+ "lint": "eslint .",
97
+ "typecheck": "tsc --noEmit",
98
+ "test": "vitest run --config vitest.config.ts",
99
+ "pack:test": "node scripts/pack-test.mjs"
100
+ }
101
+ }