@sequenceholdings/artifact-studio 0.1.12 → 0.2.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/VERSION-PIN.md +54 -0
- package/dist/active-deploy-policy.d.ts +36 -0
- package/dist/active-deploy-policy.js +66 -0
- package/dist/api.js +59 -13
- package/dist/artifact-ignore.d.ts +16 -0
- package/dist/artifact-ignore.js +38 -0
- package/dist/atlas-ui-css-plugin.d.ts +15 -0
- package/dist/atlas-ui-css-plugin.js +69 -0
- package/dist/build-subprocess.d.ts +3 -1
- package/dist/build-subprocess.js +28 -6
- package/dist/build.d.ts +25 -0
- package/dist/build.js +97 -50
- package/dist/child-environment.d.ts +5 -0
- package/dist/child-environment.js +83 -0
- package/dist/cli.d.ts +40 -1
- package/dist/cli.js +173 -87
- package/dist/config.d.ts +0 -9
- package/dist/config.js +0 -18
- package/dist/deployment-validation.d.ts +9 -0
- package/dist/deployment-validation.js +70 -0
- package/dist/git-clone.js +16 -10
- package/dist/peer-versions.d.ts +19 -0
- package/dist/peer-versions.js +44 -0
- package/dist/prepare-build.d.ts +2 -0
- package/dist/prepare-build.js +27 -7
- package/dist/project.d.ts +3 -0
- package/dist/project.js +22 -6
- package/dist/runtime-chunks.d.ts +4 -0
- package/dist/runtime-chunks.js +15 -0
- package/dist/sdk.d.ts +10 -0
- package/dist/source-resolver.d.ts +8 -5
- package/dist/source-resolver.js +81 -32
- package/dist/templates/react-vite/AGENTS.md +90 -0
- package/dist/templates/react-vite/CLAUDE.md +4 -63
- package/dist/templates/react-vite/src/main.tsx +7 -1
- package/dist/templates/react-vite/src/styles.css +11 -0
- package/package.json +18 -5
- package/templates/react-vite/AGENTS.md +90 -0
- package/templates/react-vite/CLAUDE.md +4 -63
- package/templates/react-vite/src/main.tsx +7 -1
- package/templates/react-vite/src/styles.css +11 -0
- package/dist/auth.d.ts +0 -2
- package/dist/auth.js +0 -129
|
@@ -7,7 +7,13 @@ import './styles.css'
|
|
|
7
7
|
|
|
8
8
|
const queryClient = new QueryClient()
|
|
9
9
|
|
|
10
|
-
createRoot(document.getElementById('root')!)
|
|
10
|
+
const root = createRoot(document.getElementById('root')!)
|
|
11
|
+
// Platform (same-document) runtime: register so the host can unmount on
|
|
12
|
+
// navigation. No-op inside the sandboxed iframe (registry is never installed).
|
|
13
|
+
;(window as Window & { __atlasArtifactReactRoots__?: Set<{ unmount: () => void }> })
|
|
14
|
+
.__atlasArtifactReactRoots__?.add(root)
|
|
15
|
+
|
|
16
|
+
root.render(
|
|
11
17
|
<React.StrictMode>
|
|
12
18
|
<QueryClientProvider client={queryClient}>
|
|
13
19
|
<PortalContainerProvider>
|
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@import "tw-animate-css";
|
|
3
3
|
@import "@sequenceholdings/atlas-ui/tokens.css";
|
|
4
|
+
@import "@sequenceholdings/atlas-ui/tokens.v2.css";
|
|
5
|
+
@import "@sequenceholdings/atlas-ui/theme.v2.css";
|
|
6
|
+
|
|
7
|
+
/* Tailwind v4 skips node_modules during automatic source detection, so the
|
|
8
|
+
utility classes compiled into atlas-ui's components are never generated under
|
|
9
|
+
raw `pnpm dev`. The deploy build auto-injects this @source and the v2 sheets
|
|
10
|
+
above when missing (build.ts → atlas-ui-css-plugin); declare them here so
|
|
11
|
+
scaffolded artifacts render correctly in local vite dev too. Idempotent with
|
|
12
|
+
build-time injection. At DES-227 cutover, tokens.css becomes v2 — drop the
|
|
13
|
+
explicit tokens.v2 / theme.v2 imports (and the CLI stops injecting them). */
|
|
14
|
+
@source "../node_modules/@sequenceholdings/atlas-ui/dist";
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.
|
|
12
|
+
"version": "0.2.0",
|
|
13
13
|
"description": "SDK types and CLI library for building Artifact Studio apps. Driven via `seq-studio artifact <sub>` (@sequenceholdings/studio-cli), which runs the `./cli` runCli export.",
|
|
14
14
|
"type": "module",
|
|
15
15
|
"exports": {
|
|
@@ -48,11 +48,24 @@
|
|
|
48
48
|
"./trusted-install": {
|
|
49
49
|
"types": "./dist/trusted-install.d.ts",
|
|
50
50
|
"default": "./dist/trusted-install.js"
|
|
51
|
+
},
|
|
52
|
+
"./deployment-validation": {
|
|
53
|
+
"types": "./dist/deployment-validation.d.ts",
|
|
54
|
+
"default": "./dist/deployment-validation.js"
|
|
55
|
+
},
|
|
56
|
+
"./active-deploy-policy": {
|
|
57
|
+
"types": "./dist/active-deploy-policy.d.ts",
|
|
58
|
+
"default": "./dist/active-deploy-policy.js"
|
|
59
|
+
},
|
|
60
|
+
"./child-environment": {
|
|
61
|
+
"types": "./dist/child-environment.d.ts",
|
|
62
|
+
"default": "./dist/child-environment.js"
|
|
51
63
|
}
|
|
52
64
|
},
|
|
53
65
|
"files": [
|
|
54
66
|
"dist",
|
|
55
|
-
"templates"
|
|
67
|
+
"templates",
|
|
68
|
+
"VERSION-PIN.md"
|
|
56
69
|
],
|
|
57
70
|
"dependencies": {
|
|
58
71
|
"@tailwindcss/postcss": "^4.1.8",
|
|
@@ -67,8 +80,8 @@
|
|
|
67
80
|
"tw-animate-css": "^1.4.0",
|
|
68
81
|
"vite": "8.0.16",
|
|
69
82
|
"zod": "^4.1.13",
|
|
70
|
-
"@sequenceholdings/
|
|
71
|
-
"@sequenceholdings/
|
|
83
|
+
"@sequenceholdings/lattice-form-renderer": "0.1.1",
|
|
84
|
+
"@sequenceholdings/atlas-ui": "0.3.1"
|
|
72
85
|
},
|
|
73
86
|
"devDependencies": {
|
|
74
87
|
"@types/js-yaml": "^4.0.9",
|
|
@@ -77,7 +90,7 @@
|
|
|
77
90
|
"vitest": "^4.1.2"
|
|
78
91
|
},
|
|
79
92
|
"scripts": {
|
|
80
|
-
"build": "tsc && node scripts/copy-templates.mjs",
|
|
93
|
+
"build": "rm -rf dist && tsc && node scripts/copy-templates.mjs",
|
|
81
94
|
"type-check": "tsc --noEmit",
|
|
82
95
|
"test": "vitest run",
|
|
83
96
|
"bench:build-isolation": "node --expose-gc scripts/bench-build-isolation.mjs"
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# Sequence Artifact Studio app
|
|
2
|
+
|
|
3
|
+
This is a sandboxed iframe SPA that runs inside Atlas and talks to Atlas backend routes through a postMessage bridge exposed as `seq.api.*` from `@sequenceholdings/artifact-studio`. No direct network access, no API keys in the bundle — everything goes through the bridge, and every endpoint the artifact uses must be declared in `artifact.bundle.yml`.
|
|
4
|
+
|
|
5
|
+
## Three rules that bite
|
|
6
|
+
|
|
7
|
+
1. **Envelope unwrap.** Atlas wraps every CRM-style response in `{ success: true, data: T }`. The bridge passes that envelope through raw — it does **not** unwrap automatically. Route every read/write through `unwrap<T>()` in `src/lib/api.ts`:
|
|
8
|
+
```ts
|
|
9
|
+
import { unwrap, seq } from './lib/api'
|
|
10
|
+
const customer = await unwrap<Customer>(seq.api.get(`/api/crm/customers/${id}`))
|
|
11
|
+
```
|
|
12
|
+
For endpoints with extra envelope fields (pagination `meta`, warnings), drop to `seq.api.fetch` and read `res.body` directly.
|
|
13
|
+
|
|
14
|
+
2. **FormData blocker.** `seq.api.fetch` `JSON.stringify`s any non-string `body` value, which silently turns `FormData` into `"{}"`. For multipart uploads, use `seq.api.upload(path, file, fields?, options?)`; the bridge constructs `FormData` in the trusted host and returns the usual `{ ok, status, body }` envelope.
|
|
15
|
+
|
|
16
|
+
3. **Capability matcher: exact paths, `/*` prefix wildcards, and single-segment `*` patterns.** `{id}` template forms do **not** match. List both the bare path and the prefix wildcard for broad resources, or use a least-privilege segment pattern for dynamic routes:
|
|
17
|
+
```yaml
|
|
18
|
+
- /api/crm/customers
|
|
19
|
+
- /api/crm/customers/*
|
|
20
|
+
- /api/lattice/runs/*/advance
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Prerequisite
|
|
24
|
+
|
|
25
|
+
None — `seq-studio artifact init <dir>` works from any directory; no monorepo checkout is required. The template lists `@sequenceholdings/artifact-studio` and `@sequenceholdings/atlas-ui` in **devDependencies** for local editor/type-check tooling only. `seq-studio artifact build|deploy` bundles against the CLI's own pinned copies of `react`, `react-dom`, `@tanstack/react-query`, `react-router-dom`, `sonner`, and `@sequenceholdings/atlas-ui`, and resolves `@sequenceholdings/artifact-studio` to a virtual `window.seq` module. Remote deploys (`--repo` / `--git-url`) run `pnpm install --prod` for **app-specific** `dependencies` only (e.g. `recharts`, `reactflow`) from the artifact lockfile.
|
|
26
|
+
|
|
27
|
+
## Package manager
|
|
28
|
+
|
|
29
|
+
Use **pnpm** for artifacts you will deploy from git — `seq-studio artifact build|deploy --repo/--git-url` runs `pnpm install --frozen-lockfile --prod` when `package.json` is present, so commit `pnpm-lock.yaml`. Local editor-only installs can use another package manager, but pnpm keeps Sequence's `minimumReleaseAge` supply-chain quarantine in effect (see the studio-cli README). If you have a stale checkout from the old `file:`-link era, `rm -rf node_modules` and reinstall.
|
|
30
|
+
|
|
31
|
+
## Key files
|
|
32
|
+
|
|
33
|
+
| File | Role |
|
|
34
|
+
|---|---|
|
|
35
|
+
| `artifact.bundle.yml` | Capability manifest — every API path the artifact calls: exact paths, `/*` prefix wildcards, or single-segment `*` patterns |
|
|
36
|
+
| `src/main.tsx` | Providers: `QueryClientProvider` → `PortalContainerProvider` → `App` |
|
|
37
|
+
| `src/App.tsx` | Entrypoint UI |
|
|
38
|
+
| `src/lib/api.ts` | `unwrap<T>` envelope helper + re-export of `seq` |
|
|
39
|
+
| `src/styles.css` | Tailwind v4 + `@sequenceholdings/atlas-ui/tokens.css` (+ v2 sheets; build injects them when missing) |
|
|
40
|
+
| `vite.config.ts` | Vite + React + Tailwind v4 plugin |
|
|
41
|
+
|
|
42
|
+
## Defaults
|
|
43
|
+
|
|
44
|
+
- UI: prefer `@sequenceholdings/atlas-ui` primitives (`Button`, `Card`, `Input`, `Dialog`, `Select`, `Checkbox`, `DropdownMenu`, `KpiTile`, `Switch`, …) over raw Tailwind. The design-system cutover made `Select`, `Checkbox`, `DropdownMenu`, `Command`, `Calendar`, `ScrollArea`, `Table`, and the Task family first-class exports — use them directly; fall back to `Popover` compositions or native controls only when a component is genuinely missing.
|
|
45
|
+
- Routing: `HashRouter`. Mount routes at `/`, not at the Atlas app prefix.
|
|
46
|
+
- Data: React Query is wired in `src/main.tsx`. Use `useQuery` / `useMutation` against `unwrap(seq.api.*)`.
|
|
47
|
+
- Icons: do **not** add third-party deps like `@tabler/icons-react`. Deploys from git (`--repo` / `--git-url`) build a bare tree — `seq-studio` runs `pnpm install --prod` from your lockfile, then aliases platform peers (`react`, `react-dom`, `@tanstack/react-query`, `react-router-dom`, `sonner`, `@sequenceholdings/atlas-ui`). List only app-specific imports (e.g. `recharts`) in `dependencies`; do not add file/workspace `@sequenceholdings/*` specs in git-service repos, because remote installs only accept registry semver specs. Inline SVG components in `src/icons.tsx`.
|
|
48
|
+
|
|
49
|
+
## Tokens — colour comes from semantic roles, never a raw ramp
|
|
50
|
+
|
|
51
|
+
This app bundles `@sequenceholdings/atlas-ui/tokens.css` and re-themes per tenant for free. Reach for a **semantic role** and colour follows the host brand and light/dark automatically.
|
|
52
|
+
|
|
53
|
+
**Never** use the numbered `black-*` / `white-*` opacity scales (`text-black-40`, `bg-black-6`, `border-black-10`), raw Tailwind neutrals (`gray|slate|zinc|neutral|stone-N`), arbitrary hex in class strings (`bg-[#1f2937]`), or `var(--black-*)` / `var(--color-black-*)`. These are **retired** — post-cutover they resolve to nothing or to a fixed ink that won't flip in dark. Bare `text-white` / `bg-black` and opacity modifiers like `bg-foreground/10` stay legal; only the numbered steps and raw palettes are banned. There is no old→new lookup table on purpose — pick by *what the element is and what it sits on*.
|
|
54
|
+
|
|
55
|
+
| Situation | Role |
|
|
56
|
+
|---|---|
|
|
57
|
+
| The page surface | `bg-background` — the page only, never a panel |
|
|
58
|
+
| Bordered/rounded panel, card, list | `bg-card` — a border is not a surface; every bordered container needs an explicit surface |
|
|
59
|
+
| Inset well inside a card (code block, read-only value) | `bg-muted` |
|
|
60
|
+
| Form field (input, textarea, select trigger) | `bg-field` |
|
|
61
|
+
| Interactive / hover fill | `bg-accent` / `bg-surface-hover` |
|
|
62
|
+
| Selected / active / focus state fill | `bg-foreground/8`–`bg-foreground/15` overlay |
|
|
63
|
+
| Modal scrim behind a dialog | `bg-overlay` — never `bg-foreground/N` (washes lighter in dark) |
|
|
64
|
+
| Primary text | `text-foreground` |
|
|
65
|
+
| Resting nav items, meta rows | `text-foreground-secondary` |
|
|
66
|
+
| Section labels, captions | `text-muted-foreground` |
|
|
67
|
+
| Standard border | `border-border` (form controls: `border-input`) |
|
|
68
|
+
|
|
69
|
+
Surfaces **nest** — never repeat a rung (`bg-card` inside `bg-card` is invisible): `bg-background` (page) → `bg-card` (panel, + border + `shadow-xs` to lift) → `bg-muted` (inset well).
|
|
70
|
+
|
|
71
|
+
**Dual-runtime.** Inside Atlas the artifact runs in its own iframe and inherits nothing — the runtime posts the host's resolved brand in as inline custom properties (theme channel + `data-tokens`). Standalone `vite dev` has no host, so the artifact must still render on the base semantic roles. **Do not** bundle `tokens.v2.tenants.css`, hardcode an opco's brand, or add `[data-theme]` selectors — that pins the artifact to one tenant.
|
|
72
|
+
|
|
73
|
+
## Bridge methods
|
|
74
|
+
|
|
75
|
+
Two primitives on `seq.api`:
|
|
76
|
+
|
|
77
|
+
- `seq.api.fetch(path, options?)` returns `{ ok, status, body }` — does not throw on non-2xx. Use when you need the status (e.g. handle 409) or the raw envelope with extra fields like `meta`.
|
|
78
|
+
- `seq.api.request(method, path, body?)` throws on non-2xx and returns parsed JSON. Sugar wrappers `seq.api.get / .post / .patch / .delete` are thin shells around `request`, so they also throw.
|
|
79
|
+
|
|
80
|
+
Wrap reads/writes that should auto-throw through `unwrap()` in `src/lib/api.ts`. Drop to `seq.api.fetch` when you need pagination `meta` or status inspection.
|
|
81
|
+
|
|
82
|
+
## Lattice embed context
|
|
83
|
+
|
|
84
|
+
When the artifact is served from a Lattice human node (the **Tasks/Inbox** task surface, or the builder Artifacts tab), `seq.lattice.context()` tells you which run/node/step you're rendering for:
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
const { runId, nodeId, stepId, processId } = seq.lattice.context()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
It's **synchronous** (the host injects this into the sandbox up front — no bridge round trip) and each field is `null` when unavailable. The Inbox provides all four; the builder Artifacts tab has no live `stepId`; a standalone open has no context at all. Use it instead of hand-parsing `window.location` — that's the sanctioned, stable accessor, and you don't need to declare anything in `artifact.bundle.yml` for it. To read step-scoped data, pass `runId`/`stepId` to the relevant Atlas endpoint (which still authorizes the caller server-side).
|
|
@@ -1,66 +1,7 @@
|
|
|
1
1
|
# Sequence Artifact Studio app
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agent instructions for this artifact live in `AGENTS.md` — one file every coding agent reads
|
|
4
|
+
(Cursor and Codex load `AGENTS.md` natively; the import below pulls it in for Claude Code).
|
|
5
|
+
Add or edit guidance in `AGENTS.md`, not here.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
1. **Envelope unwrap.** Atlas wraps every CRM-style response in `{ success: true, data: T }`. The bridge passes that envelope through raw — it does **not** unwrap automatically. Route every read/write through `unwrap<T>()` in `src/lib/api.ts`:
|
|
8
|
-
```ts
|
|
9
|
-
import { unwrap, seq } from './lib/api'
|
|
10
|
-
const customer = await unwrap<Customer>(seq.api.get(`/api/crm/customers/${id}`))
|
|
11
|
-
```
|
|
12
|
-
For endpoints with extra envelope fields (pagination `meta`, warnings), drop to `seq.api.fetch` and read `res.body` directly.
|
|
13
|
-
|
|
14
|
-
2. **FormData blocker.** `seq.api.fetch` `JSON.stringify`s any non-string `body` value, which silently turns `FormData` into `"{}"`. For uploads, base64-encode the bytes and POST as JSON, or patch the Atlas bridge first.
|
|
15
|
-
|
|
16
|
-
3. **Capability matcher: exact paths, `/*` prefix wildcards, and single-segment `*` patterns.** `{id}` template forms do **not** match. List both the bare path and the prefix wildcard for broad resources, or use a least-privilege segment pattern for dynamic routes:
|
|
17
|
-
```yaml
|
|
18
|
-
- /api/crm/customers
|
|
19
|
-
- /api/crm/customers/*
|
|
20
|
-
- /api/lattice/runs/*/advance
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## Prerequisite
|
|
24
|
-
|
|
25
|
-
None — `seq-studio artifact init <dir>` works from any directory; no monorepo checkout is required. The template lists `@sequenceholdings/artifact-studio` and `@sequenceholdings/atlas-ui` in **devDependencies** for local editor/type-check tooling only. `seq-studio artifact build|deploy` bundles against the CLI's own pinned copies of `react`, `react-dom`, `@tanstack/react-query`, `react-router-dom`, `sonner`, and `@sequenceholdings/atlas-ui`, and resolves `@sequenceholdings/artifact-studio` to a virtual `window.seq` module. Remote deploys (`--repo` / `--git-url`) run `pnpm install --prod` for **app-specific** `dependencies` only (e.g. `recharts`, `reactflow`) from the artifact lockfile.
|
|
26
|
-
|
|
27
|
-
## Package manager
|
|
28
|
-
|
|
29
|
-
Use **pnpm** for artifacts you will deploy from git — `seq-studio artifact build|deploy --repo/--git-url` runs `pnpm install --frozen-lockfile --prod` when `package.json` is present, so commit `pnpm-lock.yaml`. Local editor-only installs can use another package manager, but pnpm keeps Sequence's `minimumReleaseAge` supply-chain quarantine in effect (see the studio-cli README). If you have a stale checkout from the old `file:`-link era, `rm -rf node_modules` and reinstall.
|
|
30
|
-
|
|
31
|
-
## Key files
|
|
32
|
-
|
|
33
|
-
| File | Role |
|
|
34
|
-
|---|---|
|
|
35
|
-
| `artifact.bundle.yml` | Capability manifest — every API path the artifact calls: exact paths, `/*` prefix wildcards, or single-segment `*` patterns |
|
|
36
|
-
| `src/main.tsx` | Providers: `QueryClientProvider` → `PortalContainerProvider` → `App` |
|
|
37
|
-
| `src/App.tsx` | Entrypoint UI |
|
|
38
|
-
| `src/lib/api.ts` | `unwrap<T>` envelope helper + re-export of `seq` |
|
|
39
|
-
| `src/styles.css` | Tailwind v4 + `@sequenceholdings/atlas-ui/tokens.css` |
|
|
40
|
-
| `vite.config.ts` | Vite + React + Tailwind v4 plugin |
|
|
41
|
-
|
|
42
|
-
## Defaults
|
|
43
|
-
|
|
44
|
-
- UI: prefer `@sequenceholdings/atlas-ui` primitives (`Button`, `Card`, `Input`, `Dialog`, `Select`, `Checkbox`, `DropdownMenu`, `KpiTile`, `Switch`, …) over raw Tailwind. The design-system cutover made `Select`, `Checkbox`, `DropdownMenu`, `Command`, `Calendar`, `DatePicker`, `ScrollArea`, `Table`, and the Task family first-class exports — use them directly; fall back to `Popover` compositions or native controls only when a component is genuinely missing.
|
|
45
|
-
- Routing: `HashRouter`. Mount routes at `/`, not at the Atlas app prefix.
|
|
46
|
-
- Data: React Query is wired in `src/main.tsx`. Use `useQuery` / `useMutation` against `unwrap(seq.api.*)`.
|
|
47
|
-
- Icons: do **not** add third-party deps like `@tabler/icons-react`. Deploys from git (`--repo` / `--git-url`) build a bare tree — `seq-studio` runs `pnpm install --prod` from your lockfile, then aliases platform peers (`react`, `react-dom`, `@tanstack/react-query`, `react-router-dom`, `sonner`, `@sequenceholdings/atlas-ui`). List only app-specific imports (e.g. `recharts`) in `dependencies`; do not add file/workspace `@sequenceholdings/*` specs in git-service repos, because remote installs only accept registry semver specs. Inline SVG components in `src/icons.tsx`.
|
|
48
|
-
|
|
49
|
-
## Bridge methods
|
|
50
|
-
|
|
51
|
-
Two primitives on `seq.api`:
|
|
52
|
-
|
|
53
|
-
- `seq.api.fetch(path, options?)` returns `{ ok, status, body }` — does not throw on non-2xx. Use when you need the status (e.g. handle 409) or the raw envelope with extra fields like `meta`.
|
|
54
|
-
- `seq.api.request(method, path, body?)` throws on non-2xx and returns parsed JSON. Sugar wrappers `seq.api.get / .post / .patch / .delete` are thin shells around `request`, so they also throw.
|
|
55
|
-
|
|
56
|
-
Wrap reads/writes that should auto-throw through `unwrap()` in `src/lib/api.ts`. Drop to `seq.api.fetch` when you need pagination `meta` or status inspection.
|
|
57
|
-
|
|
58
|
-
## Lattice embed context
|
|
59
|
-
|
|
60
|
-
When the artifact is served from a Lattice human node (the **Tasks/Inbox** task surface, or the builder Artifacts tab), `seq.lattice.context()` tells you which run/node/step you're rendering for:
|
|
61
|
-
|
|
62
|
-
```ts
|
|
63
|
-
const { runId, nodeId, stepId, processId } = seq.lattice.context()
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
It's **synchronous** (the host injects this into the sandbox up front — no bridge round trip) and each field is `null` when unavailable. The Inbox provides all four; the builder Artifacts tab has no live `stepId`; a standalone open has no context at all. Use it instead of hand-parsing `window.location` — that's the sanctioned, stable accessor, and you don't need to declare anything in `artifact.bundle.yml` for it. To read step-scoped data, pass `runId`/`stepId` to the relevant Atlas endpoint (which still authorizes the caller server-side).
|
|
7
|
+
@AGENTS.md
|
|
@@ -7,7 +7,13 @@ import './styles.css'
|
|
|
7
7
|
|
|
8
8
|
const queryClient = new QueryClient()
|
|
9
9
|
|
|
10
|
-
createRoot(document.getElementById('root')!)
|
|
10
|
+
const root = createRoot(document.getElementById('root')!)
|
|
11
|
+
// Platform (same-document) runtime: register so the host can unmount on
|
|
12
|
+
// navigation. No-op inside the sandboxed iframe (registry is never installed).
|
|
13
|
+
;(window as Window & { __atlasArtifactReactRoots__?: Set<{ unmount: () => void }> })
|
|
14
|
+
.__atlasArtifactReactRoots__?.add(root)
|
|
15
|
+
|
|
16
|
+
root.render(
|
|
11
17
|
<React.StrictMode>
|
|
12
18
|
<QueryClientProvider client={queryClient}>
|
|
13
19
|
<PortalContainerProvider>
|
|
@@ -1,3 +1,14 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@import "tw-animate-css";
|
|
3
3
|
@import "@sequenceholdings/atlas-ui/tokens.css";
|
|
4
|
+
@import "@sequenceholdings/atlas-ui/tokens.v2.css";
|
|
5
|
+
@import "@sequenceholdings/atlas-ui/theme.v2.css";
|
|
6
|
+
|
|
7
|
+
/* Tailwind v4 skips node_modules during automatic source detection, so the
|
|
8
|
+
utility classes compiled into atlas-ui's components are never generated under
|
|
9
|
+
raw `pnpm dev`. The deploy build auto-injects this @source and the v2 sheets
|
|
10
|
+
above when missing (build.ts → atlas-ui-css-plugin); declare them here so
|
|
11
|
+
scaffolded artifacts render correctly in local vite dev too. Idempotent with
|
|
12
|
+
build-time injection. At DES-227 cutover, tokens.css becomes v2 — drop the
|
|
13
|
+
explicit tokens.v2 / theme.v2 imports (and the CLI stops injecting them). */
|
|
14
|
+
@source "../node_modules/@sequenceholdings/atlas-ui/dist";
|
package/dist/auth.d.ts
DELETED
package/dist/auth.js
DELETED
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
-
import { createServer } from 'node:http';
|
|
3
|
-
import { spawn } from 'node:child_process';
|
|
4
|
-
import { writeTokenConfig, readTokenConfig } from './config.js';
|
|
5
|
-
const REDIRECT_PORT = 5099;
|
|
6
|
-
const REDIRECT_URI = `http://localhost:${REDIRECT_PORT}`;
|
|
7
|
-
const DEFAULT_AUTH0_DOMAIN = 'dev-n1t8ts403fp8oyxp.us.auth0.com';
|
|
8
|
-
const DEFAULT_AUTH0_CLIENT_ID = 'GD9riCDWocfc66odpWBjwBiX43qqAX8r';
|
|
9
|
-
const DEFAULT_AUTH0_AUDIENCE = 'https://api.studio.com';
|
|
10
|
-
function envOr(name, fallback) {
|
|
11
|
-
return process.env[name]?.trim() || fallback;
|
|
12
|
-
}
|
|
13
|
-
function readAuth0Config() {
|
|
14
|
-
return {
|
|
15
|
-
domain: envOr('ARTIFACT_STUDIO_AUTH0_DOMAIN', DEFAULT_AUTH0_DOMAIN),
|
|
16
|
-
clientId: envOr('ARTIFACT_STUDIO_AUTH0_CLIENT_ID', DEFAULT_AUTH0_CLIENT_ID),
|
|
17
|
-
audience: envOr('ARTIFACT_STUDIO_AUTH0_AUDIENCE', DEFAULT_AUTH0_AUDIENCE),
|
|
18
|
-
};
|
|
19
|
-
}
|
|
20
|
-
function base64Url(input) {
|
|
21
|
-
return input.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
22
|
-
}
|
|
23
|
-
function openBrowser(url) {
|
|
24
|
-
const command = process.platform === 'darwin'
|
|
25
|
-
? 'open'
|
|
26
|
-
: process.platform === 'win32'
|
|
27
|
-
? 'cmd'
|
|
28
|
-
: 'xdg-open';
|
|
29
|
-
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
30
|
-
const child = spawn(command, args, { stdio: 'ignore', detached: true });
|
|
31
|
-
child.unref();
|
|
32
|
-
}
|
|
33
|
-
export async function loginWithPkce() {
|
|
34
|
-
const config = readAuth0Config();
|
|
35
|
-
const verifier = base64Url(randomBytes(32));
|
|
36
|
-
const challenge = base64Url(createHash('sha256').update(verifier).digest());
|
|
37
|
-
const authUrl = new URL(`https://${config.domain}/authorize`);
|
|
38
|
-
authUrl.searchParams.set('response_type', 'code');
|
|
39
|
-
authUrl.searchParams.set('client_id', config.clientId);
|
|
40
|
-
authUrl.searchParams.set('redirect_uri', REDIRECT_URI);
|
|
41
|
-
authUrl.searchParams.set('scope', 'openid profile email offline_access');
|
|
42
|
-
authUrl.searchParams.set('audience', config.audience);
|
|
43
|
-
authUrl.searchParams.set('code_challenge', challenge);
|
|
44
|
-
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
45
|
-
const code = await waitForCode(authUrl.toString());
|
|
46
|
-
const tokenResponse = await fetch(`https://${config.domain}/oauth/token`, {
|
|
47
|
-
method: 'POST',
|
|
48
|
-
headers: { 'Content-Type': 'application/json' },
|
|
49
|
-
body: JSON.stringify({
|
|
50
|
-
grant_type: 'authorization_code',
|
|
51
|
-
client_id: config.clientId,
|
|
52
|
-
code,
|
|
53
|
-
redirect_uri: REDIRECT_URI,
|
|
54
|
-
code_verifier: verifier,
|
|
55
|
-
}),
|
|
56
|
-
});
|
|
57
|
-
if (!tokenResponse.ok) {
|
|
58
|
-
throw new Error(`Auth0 token exchange failed: ${await tokenResponse.text()}`);
|
|
59
|
-
}
|
|
60
|
-
const tokens = await tokenResponse.json();
|
|
61
|
-
if (!tokens.refresh_token) {
|
|
62
|
-
throw new Error('No refresh token returned. Ensure Auth0 offline access is enabled.');
|
|
63
|
-
}
|
|
64
|
-
await writeTokenConfig({
|
|
65
|
-
accessToken: tokens.access_token,
|
|
66
|
-
refreshToken: tokens.refresh_token,
|
|
67
|
-
expiresAt: Date.now() + (tokens.expires_in ?? 86400) * 1000,
|
|
68
|
-
});
|
|
69
|
-
}
|
|
70
|
-
export async function getAccessToken() {
|
|
71
|
-
const tokens = await readTokenConfig();
|
|
72
|
-
if (!tokens.accessToken && !tokens.refreshToken)
|
|
73
|
-
return null;
|
|
74
|
-
if (tokens.accessToken && tokens.expiresAt && Date.now() < tokens.expiresAt - 60_000) {
|
|
75
|
-
return tokens.accessToken;
|
|
76
|
-
}
|
|
77
|
-
if (!tokens.refreshToken)
|
|
78
|
-
return tokens.accessToken ?? null;
|
|
79
|
-
const config = readAuth0Config();
|
|
80
|
-
const response = await fetch(`https://${config.domain}/oauth/token`, {
|
|
81
|
-
method: 'POST',
|
|
82
|
-
headers: { 'Content-Type': 'application/json' },
|
|
83
|
-
body: JSON.stringify({
|
|
84
|
-
grant_type: 'refresh_token',
|
|
85
|
-
client_id: config.clientId,
|
|
86
|
-
refresh_token: tokens.refreshToken,
|
|
87
|
-
}),
|
|
88
|
-
});
|
|
89
|
-
if (!response.ok) {
|
|
90
|
-
throw new Error('Stored refresh token is no longer valid. Run seq-studio login again.');
|
|
91
|
-
}
|
|
92
|
-
const refreshed = await response.json();
|
|
93
|
-
await writeTokenConfig({
|
|
94
|
-
accessToken: refreshed.access_token,
|
|
95
|
-
refreshToken: refreshed.refresh_token ?? tokens.refreshToken,
|
|
96
|
-
expiresAt: Date.now() + (refreshed.expires_in ?? 86400) * 1000,
|
|
97
|
-
});
|
|
98
|
-
return refreshed.access_token;
|
|
99
|
-
}
|
|
100
|
-
async function waitForCode(authUrl) {
|
|
101
|
-
return new Promise((resolve, reject) => {
|
|
102
|
-
const server = createServer((req, res) => {
|
|
103
|
-
const url = new URL(req.url ?? '/', REDIRECT_URI);
|
|
104
|
-
const code = url.searchParams.get('code');
|
|
105
|
-
const error = url.searchParams.get('error');
|
|
106
|
-
if (error) {
|
|
107
|
-
res.writeHead(400, { 'content-type': 'text/plain' });
|
|
108
|
-
res.end(`Login failed: ${error}`);
|
|
109
|
-
server.close();
|
|
110
|
-
reject(new Error(`Auth0 login failed: ${error}`));
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
if (!code) {
|
|
114
|
-
res.writeHead(400, { 'content-type': 'text/plain' });
|
|
115
|
-
res.end('Missing authorization code');
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
res.writeHead(200, { 'content-type': 'text/html' });
|
|
119
|
-
res.end('<h1>Artifact Studio login complete</h1><p>You may close this tab.</p>');
|
|
120
|
-
server.close();
|
|
121
|
-
resolve(code);
|
|
122
|
-
});
|
|
123
|
-
server.once('error', reject);
|
|
124
|
-
server.listen(REDIRECT_PORT, () => {
|
|
125
|
-
console.log(`Opening browser for Auth0 login. If it does not open, visit:\n${authUrl}`);
|
|
126
|
-
openBrowser(authUrl);
|
|
127
|
-
});
|
|
128
|
-
});
|
|
129
|
-
}
|