@sevenfold/setto-client 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -8,29 +8,29 @@ Editors authenticate via Supabase, edit text inline on the live page, pick secti
8
8
 
9
9
  ## Install
10
10
 
11
- ```bash
12
- bun add @setto/client@npm:@sevenfold/setto-client
13
- ```
14
-
15
- Or add directly to `package.json`:
11
+ Sevenfold sites pin an npm version via alias:
16
12
 
17
13
  ```json
18
- "@setto/client": "npm:@sevenfold/setto-client@^0.1.0"
14
+ "@setto/client": "npm:@sevenfold/setto-client@0.5.0"
19
15
  ```
20
16
 
21
- Peer deps: `react`, `react-dom`, `react-i18next`, `i18next`.
17
+ `bun install` pulls the prebuilt package from npm — no GitHub token required on Vercel.
22
18
 
23
- For local monorepo development alongside `carryon.no`, a `file:` link also works — see [Local development](#local-development-sevenfold-monorepo).
19
+ Peer deps: `react`, `react-dom`, `react-i18next`, `i18next`.
24
20
 
25
- ### Publishing a new version
21
+ For local monorepo development the consumer's `vite.config.ts` aliases `@setto/client` to setto-client source — see [Local development](#local-development-sevenfold-monorepo).
26
22
 
27
- 1. Bump `version` in `package.json`
28
- 2. Commit and push a tag: `git tag v0.1.1 && git push origin v0.1.1`
29
- 3. GitHub Actions publishes to npm automatically
23
+ ### Releasing a new version
30
24
 
31
- Requires an `NPM_TOKEN` secret in the GitHub repo (Granular Access Token from npmjs.com with write access to `@sevenfold`).
25
+ 1. Bump `version` in `package.json` following semver (patch / minor / major).
26
+ 2. Commit and push to `main`. GitHub Actions publishes `@sevenfold/setto-client` to npm automatically (requires `NPM_TOKEN` secret in this repo).
27
+ 3. Confirm the version is on npm: `npm view @sevenfold/setto-client@X.Y.Z version`.
28
+ 4. In each consumer (`carryon.no`, `setto-site`):
29
+ - Update the pin in `package.json` (e.g. `@0.5.0` → `@0.5.1`).
30
+ - Run `bun install` to refresh the lockfile.
31
+ - Commit and push — Vercel installs the new npm version on the next deploy.
32
32
 
33
- Or trigger manually via **Actions Publish to npm Run workflow**.
33
+ > **Why npm, not a git tag?** `nitech/setto-client` is private. Vercel cannot fetch `github:nitech/setto-client#v…` without a `GITHUB_TOKEN` with repo access. Public npm avoids that and ships a prebuilt `dist/`.
34
34
 
35
35
  ---
36
36
 
@@ -69,7 +69,7 @@ createRoot(document.getElementById('root')!).render(
69
69
 
70
70
  ### 2. Mount the Setto route
71
71
 
72
- Editors reach edit mode by visiting `sitenavn.no/setto`.
72
+ Editors sign in once at `sitenavn.no/setto`. After that, every visit to the public site auto-enters edit mode for as long as the Supabase session persists.
73
73
 
74
74
  ```tsx
75
75
  // App.tsx
@@ -126,24 +126,32 @@ function ValuesSection() {
126
126
 
127
127
  ## Edit mode
128
128
 
129
- Edit mode activates when **both** are true:
129
+ Edit mode activates whenever **all** of these are true:
130
130
 
131
- 1. Authenticated Supabase session.
132
- 2. URL contains `?setto=edit`.
131
+ 1. The user has an authenticated Supabase session.
132
+ 2. The user has access to `config.siteId` (a row in the `sites` table that they can read).
133
+ 3. The current path is not `/setto` (the dashboard is rendered without inline editing).
133
134
 
134
- The `/setto` dashboard does **not** activate edit mode. Use **Begynn å redigere**, which navigates to `/?setto=edit`.
135
+ Sign-in at `/setto` persists via Supabase, so subsequent visits drop straight into edit mode no URL flag, no extra step.
135
136
 
136
137
  ### What editors see
137
138
 
139
+ A small round Setto button floats in the bottom-right corner. While there are no unpublished changes it stays as a circle showing only the Setto mark. As soon as you edit something it grows into a pill containing **Publiser** and a **⋯** menu with:
140
+
141
+ | Item | Effect |
142
+ |------|--------|
143
+ | **Avbryt** | Discards every unsaved draft (text, section colours, image uploads) |
144
+ | **Logg ut** | Signs out via Supabase and exits edit mode |
145
+ | **Historikk** | Opens the `/setto` dashboard with deployment history |
146
+
138
147
  | Action | How |
139
148
  |--------|-----|
140
149
  | Edit text | Click any `<T>` element — it becomes `contentEditable` |
141
150
  | Edit section colours | Click a section or block background (not text) |
142
151
  | Follow a link | Ctrl/Cmd + click (desktop) · **Naviger ↗** chip when focused (touch) |
143
- | Publish | Top toolbar → **Publiser** |
144
- | Exit | Top toolbar → **Avslutt** (removes `?setto=edit`) |
152
+ | Publish | FAB → **Publiser** (visible once you have drafts) |
145
153
 
146
- A fixed toolbar sits at the top of the viewport. When you click a section or block, a compact colour toolbar appears above it, centred and sized to its contents. Click again or press Escape to dismiss.
154
+ When you click a section or block, a compact colour toolbar appears above it. Click again or press Escape to dismiss.
147
155
 
148
156
  ---
149
157
 
@@ -308,7 +316,7 @@ Keeping this in the repo means content shape lives with the code that defines it
308
316
 
309
317
  ## Publish flow
310
318
 
311
- 1. Editor clicks **Publiser** in the top toolbar.
319
+ 1. Editor clicks **Publiser** in the floating FAB.
312
320
  2. Client serialises changed locale bundles + `sections.json` (if theme drafts exist).
313
321
  3. `POST /sites/:siteId/publish` with `{ files: [{ path, content }] }`.
314
322
  4. setto-server validates paths against `content_paths`, commits to GitHub.
@@ -322,7 +330,7 @@ Drafts are cleared after a successful publish.
322
330
 
323
331
  Route: `/setto/*`
324
332
 
325
- Provides Supabase email/password login (invite-only — no self-service sign-up), password reset, and a dashboard with a link to start editing (`/?setto=edit`). Invite links from Supabase land on `/setto` to set a password. Does not render the inline editor itself.
333
+ Provides Supabase email/password login (invite-only — no self-service sign-up), password reset, and a dashboard. After sign-in the dashboard redirects to the site home, where edit mode auto-activates. Invite links from Supabase land on `/setto` to set a password. Does not render the inline editor itself.
326
334
 
327
335
  ---
328
336
 
@@ -0,0 +1,22 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { AnimationPreset } from './block-tree/schema';
3
+ export interface SettoAnimationProps {
4
+ preset: AnimationPreset;
5
+ /** Delay before play, in milliseconds. */
6
+ delay?: number;
7
+ /** Duration in milliseconds. Defaults per preset. */
8
+ duration?: number;
9
+ className?: string;
10
+ children: ReactNode;
11
+ }
12
+ /**
13
+ * Lightweight, CSS-driven reveal wrapper. Triggers once when scrolled into
14
+ * view (or immediately, for non-scroll presets). Honors
15
+ * `prefers-reduced-motion`, falls back to plain children with no animation.
16
+ *
17
+ * Hosts that use GSAP (carryon.no, setto-site) should keep their own
18
+ * `data-reveal` system; this primitive exists so generated layout trees
19
+ * have a portable animation primitive without dragging GSAP into the
20
+ * setto-client bundle.
21
+ */
22
+ export declare function SettoAnimation({ preset, delay, duration, className, children, }: SettoAnimationProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,18 @@
1
+ import type { FormField } from './block-tree/schema';
2
+ export interface SettoFormProps {
3
+ /** Scopes i18n keys: `form.{formId}.submit`, `.success`, `.error`. */
4
+ formId: string;
5
+ fields: FormField[];
6
+ /** Static POST endpoint. Either this or `endpointKey` must be provided. */
7
+ endpoint?: string;
8
+ /** i18n key whose value is the runtime endpoint URL. */
9
+ endpointKey?: string;
10
+ className?: string;
11
+ }
12
+ /**
13
+ * Generic POST form whose copy lives in i18n. Field structure is fixed by
14
+ * the host (or by the block tree) — editors can change labels and copy but
15
+ * not the field set, matching the Setto product principle that the editor
16
+ * adjusts content, not structure.
17
+ */
18
+ export declare function SettoForm({ formId, fields, endpoint, endpointKey, className, }: SettoFormProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,16 @@
1
+ export interface SettoVideoProps {
2
+ /** i18n key holding the video URL (YouTube, Vimeo, or direct MP4). */
3
+ srcKey: string;
4
+ /** Optional i18n key holding a poster image URL (HTML5 video only). */
5
+ posterKey?: string;
6
+ title?: string;
7
+ className?: string;
8
+ }
9
+ /**
10
+ * Renders a video from a URL stored in i18n. YouTube and Vimeo URLs become
11
+ * iframe embeds; everything else renders as an HTML5 `<video>` element.
12
+ *
13
+ * Editors can change the URL via the i18n draft store (a dedicated picker
14
+ * UI may follow; until then it's swap-via-publish like other content).
15
+ */
16
+ export declare function SettoVideo({ srcKey, posterKey, title, className }: SettoVideoProps): import("react/jsx-runtime").JSX.Element;
@@ -2,10 +2,9 @@
2
2
  * Drop-in admin SPA. Mount under a route like `<Route path="/setto/*" .../>`.
3
3
  *
4
4
  * Behaviour after login:
5
- * - Redirects to `/?setto=edit` on the site home (edit mode active).
5
+ * - Redirects to the site home `/`. Edit mode activates automatically there
6
+ * via `SettoProvider` when the user has access to `config.siteId`.
6
7
  * - Shows the dashboard only when the user lacks access to this site, or on
7
8
  * `/setto?deployment=…` for publish progress / history.
8
- *
9
- * Editing happens on the public site at `/?setto=edit`.
10
9
  */
11
10
  export declare function SettoAdminApp(): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,24 @@
1
+ import { type ComponentType } from 'react';
2
+ import { type SettoIconRenderProps } from '../SettoIcon';
3
+ import type { BlockTree } from './schema';
4
+ export interface SettoBlockTreeProps {
5
+ /** Validated block tree, typically parsed from `home.layout.json`. */
6
+ tree: BlockTree;
7
+ /**
8
+ * Icon registry consumed by `SettoIcon` nodes — the same map a host would
9
+ * pass directly to `<SettoIcon icons={…}>`.
10
+ */
11
+ icons: Record<string, ComponentType<SettoIconRenderProps>>;
12
+ /**
13
+ * When true (default), validates the tree and throws on schema errors.
14
+ * Disable in production after a one-time boot check.
15
+ */
16
+ validate?: boolean;
17
+ }
18
+ /**
19
+ * Renders a block tree by dispatching each node to the matching Setto
20
+ * primitive. Inside a `repeater`, leaf nodes use keys relative to the item;
21
+ * the renderer composes them with the active scope so the agent can author
22
+ * trees without knowing per-item suffixes.
23
+ */
24
+ export declare function SettoBlockTree({ tree, icons, validate }: SettoBlockTreeProps): import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Walks a block tree and reports the i18n keys, theme ids, and required
3
+ * sample-locale entries it expects. The agent uses this output to validate
4
+ * that the generated `no.json` covers everything the tree references.
5
+ */
6
+ import type { BlockTree } from './schema';
7
+ export interface ContentKeyRequirements {
8
+ /** Plain i18n keys that must exist with a string value. */
9
+ keys: Set<string>;
10
+ /**
11
+ * Section / block theme ids referenced by the tree. The deterministic
12
+ * sections.json overlay and section-schemas.ts must include each id.
13
+ */
14
+ themeIds: Set<string>;
15
+ /**
16
+ * Repeater requirements — at least one item entry must exist in the
17
+ * locale under `itemsKey`, with every field from `defaultItem`.
18
+ */
19
+ repeaters: Array<{
20
+ itemsKey: string;
21
+ fields: string[];
22
+ /**
23
+ * Item-relative keys produced by walking the item template. The
24
+ * validator checks every existing locale item has these keys.
25
+ */
26
+ itemKeys: string[];
27
+ }>;
28
+ }
29
+ /** Returns content requirements for an already-validated tree. */
30
+ export declare function extractContentKeys(tree: BlockTree): ContentKeyRequirements;
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Per-type metadata for block-tree nodes.
3
+ *
4
+ * The renderer dispatches on node `type`; the validator and key extractor
5
+ * consult this registry for allowed children, required content keys, and
6
+ * whether a type carries a theme id.
7
+ */
8
+ import type { BlockNode, BlockNodeType } from './schema';
9
+ export interface BlockTypeMeta {
10
+ /**
11
+ * Allowed child types. `null` means the node is a leaf; `'any'` means any
12
+ * other type may appear as a child.
13
+ */
14
+ allowedChildren: BlockNodeType[] | 'any' | null;
15
+ /** True when the node has a theme id (used in `sections.json`). */
16
+ themed: boolean;
17
+ /** True when this node participates as a `SectionToolbar` target. */
18
+ selectable: boolean;
19
+ }
20
+ export declare const BLOCK_REGISTRY: Record<BlockNodeType, BlockTypeMeta>;
21
+ /** All known block types, kept in sync with `BLOCK_REGISTRY`. */
22
+ export declare const BLOCK_TYPES: readonly BlockNodeType[];
23
+ /** Returns the children of a node, or an empty array for leaves. */
24
+ export declare function nodeChildren(node: BlockNode): BlockNode[];
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Declarative page model rendered by `SettoBlockTree`.
3
+ *
4
+ * A site's layout is a tree of typed nodes. Each node maps to one of the
5
+ * existing editable primitives (T, SettoSection, SettoBlock, SettoRepeater,
6
+ * SettoImage, SettoIcon) or to a new composite primitive (SettoForm,
7
+ * SettoVideo, SettoAnimation). The tree is stored as JSON
8
+ * (`src/content/pages/home.layout.json`) and is what `setto-agent` generates
9
+ * during onboarding instead of writing TSX.
10
+ */
11
+ export type BlockTreeVersion = '1';
12
+ export interface BlockTree {
13
+ version: BlockTreeVersion;
14
+ /** Top-level nodes rendered into `<main>` (or a host-provided wrapper). */
15
+ root: BlockNode[];
16
+ }
17
+ export type BlockNode = SectionNode | BlockBoxNode | ContainerNode | TextNode | ImageNode | IconNode | RepeaterNode | FormNode | VideoNode | AnimationNode;
18
+ export type BlockNodeType = BlockNode['type'];
19
+ interface BaseNode {
20
+ /** Stable id within the tree — used as React key and for diff/edit ops. */
21
+ id: string;
22
+ /** Optional Tailwind / CSS class string applied to the rendered element. */
23
+ className?: string;
24
+ }
25
+ export interface SectionNode extends BaseNode {
26
+ type: 'section';
27
+ /** Matches a key in `sections.json` / `sectionSchemas` for background theme. */
28
+ sectionId: string;
29
+ children: BlockNode[];
30
+ }
31
+ export interface BlockBoxNode extends BaseNode {
32
+ type: 'block';
33
+ blockId: string;
34
+ children: BlockNode[];
35
+ }
36
+ export type ContainerTag = 'div' | 'section' | 'header' | 'footer' | 'main' | 'aside' | 'nav' | 'article' | 'ul' | 'ol' | 'li' | 'p' | 'span';
37
+ export interface ContainerNode extends BaseNode {
38
+ type: 'container';
39
+ tag?: ContainerTag;
40
+ children: BlockNode[];
41
+ }
42
+ export type TextAs = 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' | 'p' | 'span' | 'div' | 'label' | 'em' | 'strong';
43
+ export interface TextNode extends BaseNode {
44
+ type: 'text';
45
+ /** i18n key, or a key relative to the surrounding repeater item. */
46
+ k: string;
47
+ /** Element tag to wrap the `<T>` in. Defaults to inline span. */
48
+ as?: TextAs;
49
+ }
50
+ export interface ImageNode extends BaseNode {
51
+ type: 'image';
52
+ /** i18n key holding the image src (resolved at runtime). */
53
+ srcKey: string;
54
+ alt?: string;
55
+ }
56
+ export interface IconNode extends BaseNode {
57
+ type: 'icon';
58
+ /** i18n key holding the icon name from the host icon registry. */
59
+ k: string;
60
+ size?: number;
61
+ }
62
+ export interface RepeaterNode extends BaseNode {
63
+ type: 'repeater';
64
+ /** i18n list prefix, e.g. 'faq.items'. */
65
+ itemsKey: string;
66
+ /** Norwegian label shown in the editor's add/remove controls. */
67
+ itemLabel?: string;
68
+ /** Field defaults seeded into i18n when an editor adds a new item. */
69
+ defaultItem: Record<string, string>;
70
+ /**
71
+ * Subtree rendered once per item. Text/image/icon nodes here may use
72
+ * keys relative to the item (e.g. `k: 'q'` resolves to
73
+ * `faq.items.{itemKey}.q` at render time).
74
+ */
75
+ itemTemplate: BlockNode[];
76
+ }
77
+ export type FormFieldType = 'text' | 'email' | 'tel' | 'textarea';
78
+ export interface FormField {
79
+ /** Submitted form field name. */
80
+ name: string;
81
+ type: FormFieldType;
82
+ required?: boolean;
83
+ /** i18n key for the field label. */
84
+ labelKey: string;
85
+ /** i18n key for the placeholder text. */
86
+ placeholderKey?: string;
87
+ }
88
+ export interface FormNode extends BaseNode {
89
+ type: 'form';
90
+ /**
91
+ * Scopes all i18n keys for this form: `form.{formId}.submit`,
92
+ * `form.{formId}.success`, `form.{formId}.error`.
93
+ */
94
+ formId: string;
95
+ /** Static POST endpoint. Use `endpointKey` to read from i18n instead. */
96
+ endpoint?: string;
97
+ /** i18n key whose value is the form POST endpoint. */
98
+ endpointKey?: string;
99
+ fields: FormField[];
100
+ }
101
+ export interface VideoNode extends BaseNode {
102
+ type: 'video';
103
+ /** i18n key holding the embed URL (YouTube, Vimeo, MP4, …). */
104
+ srcKey: string;
105
+ /** Optional i18n key for a poster image (HTML5 video only). */
106
+ posterKey?: string;
107
+ /** Iframe accessibility title. Not editable. */
108
+ title?: string;
109
+ }
110
+ export type AnimationPreset = 'fadeIn' | 'slideUp' | 'stagger' | 'scrollReveal';
111
+ export interface AnimationNode extends BaseNode {
112
+ type: 'animation';
113
+ preset: AnimationPreset;
114
+ /** Delay before play, in milliseconds. */
115
+ delay?: number;
116
+ /** Duration in milliseconds. Defaults to preset value. */
117
+ duration?: number;
118
+ children: BlockNode[];
119
+ }
120
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Runtime validator for block trees.
3
+ *
4
+ * Used by both `setto-client` (when loading a `home.layout.json` at boot)
5
+ * and `setto-agent` (after generation, before sandbox build). No external
6
+ * dependencies so we can ship to host sites without bundle bloat.
7
+ */
8
+ import type { BlockTree, FormField } from './schema';
9
+ export interface ValidationIssue {
10
+ /** Dotted path to the offending node, e.g. `root[2].children[0]`. */
11
+ path: string;
12
+ message: string;
13
+ }
14
+ export type ValidationResult = {
15
+ ok: true;
16
+ } | {
17
+ ok: false;
18
+ issues: ValidationIssue[];
19
+ };
20
+ /**
21
+ * Validates a parsed block tree. Returns the first batch of issues found —
22
+ * the goal is enough detail for a fixer model to act on, not exhaustive
23
+ * reporting.
24
+ */
25
+ export declare function validateBlockTree(input: unknown): ValidationResult;
26
+ /** Narrowing helper: validates and returns a typed tree, or throws. */
27
+ export declare function parseBlockTree(input: unknown): BlockTree;
28
+ export type { FormField };
@@ -1,2 +1,4 @@
1
- /** Height of the fixed edit toolbar (px). */
2
- export declare const TOOLBAR_HEIGHT = 44;
1
+ /** Diameter of the collapsed FAB and height of the expanded pill (px). */
2
+ export declare const FAB_SIZE = 52;
3
+ /** Distance from the viewport edges to the FAB (px). */
4
+ export declare const FAB_INSET = 16;
@@ -1,5 +1,7 @@
1
1
  /**
2
- * Reserves space at the top of the viewport for the Setto edit toolbar.
3
- * The host site keeps full width only vertical offset is applied.
2
+ * Applies the document-level CSS for inline edit mode. The fixed top toolbar
3
+ * has been replaced with a floating FAB anchored bottom-right, so the host
4
+ * page no longer needs vertical padding — only edit-affordance hover styles
5
+ * and the portal layers above the page.
4
6
  */
5
7
  export declare function useSettoDocumentLayout(active: boolean): void;
@@ -5,8 +5,8 @@ interface EditModeShellProps {
5
5
  themeStore: ThemeStore | null;
6
6
  }
7
7
  /**
8
- * Edit mode: full-width page with a fixed toolbar at the top. Text is edited
9
- * inline via contentEditable on `<T>` — no sidebar.
8
+ * Edit mode: full-width page with a floating Setto FAB anchored bottom-right.
9
+ * Text is edited inline via contentEditable on `<T>` — no sidebar.
10
10
  */
11
11
  export declare function EditModeShell({ children, themeStore }: EditModeShellProps): import("react/jsx-runtime").JSX.Element;
12
12
  /** @deprecated Use EditModeShell */
@@ -0,0 +1,16 @@
1
+ import type { CSSProperties } from 'react';
2
+ interface SettoMarkProps {
3
+ size?: number;
4
+ /** Color of the "S" letter. Defaults to the Setto crema tone. */
5
+ letterColor?: string;
6
+ /** Color of the accent dot. Defaults to the Setto terracotta. */
7
+ dotColor?: string;
8
+ style?: CSSProperties;
9
+ }
10
+ /**
11
+ * Setto brand mark — the foreground of the setto-site favicon (italic "S" with
12
+ * a terracotta dot) on a transparent background. The FAB renders the dark
13
+ * Blekk background itself so the mark only carries the glyphs.
14
+ */
15
+ export declare function SettoMark({ size, letterColor, dotColor, style, }: SettoMarkProps): import("react/jsx-runtime").JSX.Element;
16
+ export {};
package/dist/index.d.ts CHANGED
@@ -9,9 +9,22 @@ export { SettoImage } from './SettoImage';
9
9
  export type { SettoImageProps } from './SettoImage';
10
10
  export { SettoRepeater } from './SettoRepeater';
11
11
  export type { SettoRepeaterProps } from './SettoRepeater';
12
+ export { SettoForm } from './SettoForm';
13
+ export type { SettoFormProps } from './SettoForm';
14
+ export { SettoVideo } from './SettoVideo';
15
+ export type { SettoVideoProps } from './SettoVideo';
16
+ export { SettoAnimation } from './SettoAnimation';
17
+ export type { SettoAnimationProps } from './SettoAnimation';
12
18
  export { useSectionTheme } from './use-section-theme';
13
19
  export { SettoAdminApp } from './admin/App';
14
20
  export { SETTO_BASE } from './lib/urls';
15
21
  export { AuthGate } from './edit-mode/auth-gate';
16
22
  export type { SettoConfig, BrandColor, DeploymentRow, SiteRow, ContentFile, PublishFile, PublishResult, } from './types';
17
23
  export type { SectionSchema, SectionColorField } from './section-schema';
24
+ export { SettoBlockTree } from './block-tree/SettoBlockTree';
25
+ export type { SettoBlockTreeProps } from './block-tree/SettoBlockTree';
26
+ export type { BlockTree, BlockTreeVersion, BlockNode, BlockNodeType, SectionNode, BlockBoxNode, ContainerNode, ContainerTag, TextNode, TextAs, ImageNode, IconNode, RepeaterNode, FormNode, FormField, FormFieldType, VideoNode, AnimationNode, AnimationPreset, } from './block-tree/schema';
27
+ export { validateBlockTree, parseBlockTree, type ValidationIssue, type ValidationResult, } from './block-tree/validate';
28
+ export { extractContentKeys, type ContentKeyRequirements, } from './block-tree/extract-keys';
29
+ export { BLOCK_REGISTRY, BLOCK_TYPES, nodeChildren } from './block-tree/registry';
30
+ export type { BlockTypeMeta } from './block-tree/registry';
@@ -27,6 +27,8 @@ export declare class ThemeStore {
27
27
  set(sectionId: string, field: string, value: string): void;
28
28
  /** Replace baseline from GitHub without creating drafts. */
29
29
  loadBaseline(next: SectionsTheme): void;
30
+ /** Resets every draft back to its original value. */
31
+ revertAll(): void;
30
32
  commit(): void;
31
33
  size(): number;
32
34
  serialise(): SectionsTheme;
@@ -1,11 +1,15 @@
1
1
  /**
2
2
  * Base path where the Setto login + dashboard SPA is mounted on the host site.
3
- * Editors reach edit mode by visiting `sitenavn.no/setto`. The host app must
4
- * mount `<SettoAdminApp>` at this path (e.g. `<Route path="/setto/*" …>`).
3
+ * Editors reach the dashboard by visiting `sitenavn.no/setto`. The host app
4
+ * must mount `<SettoAdminApp>` at this path (e.g. `<Route path="/setto/*" …>`).
5
5
  */
6
6
  export declare const SETTO_BASE = "/setto";
7
- /** Site home with inline edit mode active. */
8
- export declare function editModeUrl(pathname?: string): string;
7
+ /**
8
+ * Site home URL used after sign-in. Edit mode itself is now driven by the
9
+ * Supabase session + site access — no URL flag required — so this just sends
10
+ * the editor to the public site root.
11
+ */
12
+ export declare function siteHomeUrl(pathname?: string): string;
9
13
  export declare function isAdminRoute(): boolean;
10
14
  /** Setto deployment progress panel (`/setto?deployment=…`). */
11
15
  export declare function isAdminDeploymentView(): boolean;
@@ -0,0 +1,16 @@
1
+ import type { Session, SupabaseClient } from '@supabase/supabase-js';
2
+ import type { SiteRow } from '../types';
3
+ export interface SiteAccessState {
4
+ /** All sites the current user can access. `null` until loaded. */
5
+ sites: SiteRow[] | null;
6
+ /** The site matching `siteId`, or `null` if the user has no access. */
7
+ currentSite: SiteRow | null;
8
+ loading: boolean;
9
+ error: string | null;
10
+ }
11
+ /**
12
+ * Loads the sites table for the current Supabase session and resolves whether
13
+ * the user has access to `siteId`. Returns a stable shape across loading,
14
+ * success, and failure so callers can read `currentSite` directly.
15
+ */
16
+ export declare function useSiteAccess(supabase: SupabaseClient, session: Session | null, siteId: string): SiteAccessState;
@@ -4,7 +4,7 @@ import { type SettoApi } from './lib/api';
4
4
  import { I18nStore } from './lib/i18n-store';
5
5
  import { AssetStore } from './lib/asset-store';
6
6
  import { ThemeStore } from './lib/theme-store';
7
- import type { SettoConfig } from './types';
7
+ import type { SettoConfig, SiteRow } from './types';
8
8
  interface SettoContextValue {
9
9
  config: SettoConfig;
10
10
  supabase: SupabaseClient;
@@ -12,8 +12,12 @@ interface SettoContextValue {
12
12
  session: Session | null;
13
13
  /** True while the initial auth state is being read from local storage. */
14
14
  authLoading: boolean;
15
- /** True when inline edit mode is active (`?setto=edit` + session). */
15
+ /** True when inline edit mode is active (session + site access). */
16
16
  editMode: boolean;
17
+ /** Site row matching `config.siteId`, or `null` if the user has no access. */
18
+ currentSite: SiteRow | null;
19
+ /** All sites the user can access (loaded after sign-in). */
20
+ sites: SiteRow[] | null;
17
21
  /**
18
22
  * Lazily created when i18next is initialised. Components that need it
19
23
  * should `if (!store) return null;` until then.
@@ -32,10 +36,11 @@ export interface SettoProviderProps {
32
36
  *
33
37
  * Edit mode is on when ALL of:
34
38
  * 1. There is an authenticated Supabase session.
35
- * 2. The URL contains `?setto=edit`.
39
+ * 2. The user has access to `config.siteId` in the Setto sites table.
40
+ * 3. The current path is not the `/setto` admin SPA.
36
41
  *
37
- * The /setto dashboard does not activate edit mode use "Begynn å redigere"
38
- * which navigates to `/?setto=edit`.
42
+ * Once an editor signs in via `/setto`, returning visits auto-enter edit mode
43
+ * for as long as the Supabase session persists.
39
44
  */
40
45
  export declare function SettoProvider({ config, children }: SettoProviderProps): import("react/jsx-runtime").JSX.Element;
41
46
  export declare function useSetto(): SettoContextValue;