@supersuit/artifacts 0.40.1 → 0.42.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/CHANGELOG.md CHANGED
@@ -3,6 +3,68 @@
3
3
  `@supersuit/artifacts`. One entry per version, newest first. Each entry says what changed, how a
4
4
  host can tell whether it is affected (DETECTOR), what a host does about it (REMEDY), and the tests.
5
5
 
6
+ ## 0.42.0
7
+
8
+ **One page can say who sees its version number and past versions: `history: everyone | admins` in
9
+ front matter.** 0.41.0 made the number follow the host's `history` setting, which is one answer for
10
+ every page on a host. A host carries both kinds of page: a letter to one person, where a version
11
+ number is noise, and a spec its readers follow from draft to draft, where it is the point. The
12
+ page's own line overrides the host's for that page; absent, nothing changes.
13
+
14
+ - `history: everyone` shows that page's readers "Version N · Updated <minute>", Version history in
15
+ the page menu and every `/<id>/v/<n>`. `history: admins` keeps all three to admins on a host
16
+ configured `history: 'everyone'`. Admins always see them.
17
+ - Content, like `toc:`: a republish without the line goes back to the host's setting.
18
+ - Refused with `links: per-reader` (and so on a broadcast), because a per-reader page serves no past
19
+ versions and the list would be dead links. A page made per-reader by an earlier publish, whose
20
+ file no longer repeats the line, has its `history: everyone` ignored at render for the same reason.
21
+ - `ArtifactMeta` and `ArtifactRecord` gain `history`; `PageHistory` and `PAGE_HISTORY` are exported
22
+ from the front matter module. `CoverProps.showVersion` follows the page's line.
23
+
24
+ DETECTOR: a publisher that validates front matter keys itself, before the host does, refuses
25
+ `history:` as unknown until it learns the key: `rg -n "comment_name" <your publisher>` finds such a
26
+ list. A custom `ArtifactStore` that copies named fields drops `history` until it carries it.
27
+
28
+ REMEDY: add `history` to such a list and to a custom store's saved fields. Nothing else: a page
29
+ without the line behaves exactly as under 0.41.0.
30
+
31
+ Tests: `src/artifacts/front-matter.test.ts` (both values, an unknown value, the per-reader and
32
+ broadcast refusals), `src/artifacts/store.test.ts` (rides through; a republish without it clears
33
+ it), `src/routes/page-menu.test.tsx` (a page's `everyone` on a default host, a page's `admins` on
34
+ an `everyone` host, and who opens a past version). The three route tests fail when the route
35
+ ignores the page's line.
36
+
37
+ ## 0.41.0
38
+
39
+ **A reader who may not see a page's history is no longer told its version number.** The line under
40
+ the summary read "Version N · Updated <minute>" to everyone. A page sent to someone reads as a
41
+ message, and "Version 9" on a message tells its reader about eight drafts they were never meant to
42
+ think about. The number now goes with the history: whoever `history` lets see past versions
43
+ (the owner and `admins` by default, every reader under `history: 'everyone'`) sees
44
+ "Version N · Updated <minute>", and everyone else sees "Updated <minute>". There is no new config
45
+ key; the existing `history` setting is the toggle, and its default is off for readers.
46
+
47
+ - `CoverProps` gains `showVersion: boolean`, true exactly when this reader may see the number. The
48
+ `meta` node a Cover places already follows it. `version` is still handed over unchanged, so a
49
+ Cover that draws the number itself keeps drawing it until it reads `showVersion`.
50
+ - A past version's page (`/<id>/v/<n>`) is unchanged: only a reader who may see the history reaches
51
+ it, so it still names its version.
52
+
53
+ DETECTOR: a host whose pack has a `Cover` that renders `version`: `rg -n "version" <your packs>`.
54
+ Such a Cover still shows the number to every reader after this release, because the shell cannot
55
+ take a prop away from a pack. A host that relied on every reader seeing the version line and does
56
+ not set `history: 'everyone'` will find the number gone for signed-out readers.
57
+
58
+ REMEDY: in a Cover that draws the number, draw it only when `showVersion` is true. To show every
59
+ reader the number (and the history with it), set `history: 'everyone'`.
60
+
61
+ Tests: `src/routes/page-menu.test.tsx` (a visitor, a reader and a stranger get "Updated" with no
62
+ version number; an admin and `history: 'everyone'` get "Version 2 · Updated"),
63
+ `src/routes/extension-points.test.tsx` (a Cover is handed `showVersion` false for a reader and true
64
+ under `history: 'everyone'`), `src/routes/versions.test.tsx` and `src/routes/broadcast.test.tsx`
65
+ (the two tests that pinned the old line now pin the new one). All three new tests failed against
66
+ 0.40.1.
67
+
6
68
  ## 0.40.1
7
69
 
8
70
  **0.40.0 was tagged and never published: this is the first release carrying it.** The publish
package/README.md CHANGED
@@ -194,7 +194,8 @@ before 0.31.0 has no `declared` and takes its file on its next publish, as every
194
194
 
195
195
  ## Version history
196
196
 
197
- Every page names its version under the summary, "Version 4 · Updated <minute>", and the page
197
+ Every page says when it was last updated under the summary, and to its admins that line also names
198
+ the version: "Version 4 · Updated <minute>". The page
198
199
  menu's **Version history** opens a panel listing every version newest first: its number, the minute it went live in
199
200
  the reader's own time zone, and its change note. The panel is drawn over the page (a sheet on a
200
201
  phone, a side panel on a desk), so opening it moves no text. Each past version is readable at
@@ -205,9 +206,23 @@ the same password or confidential door as the page itself. A version that does n
205
206
  **Past versions are for admins by default (0.21.0).** An earlier draft can hold what the current
206
207
  one was edited to remove, so only the page's admins see Version history in the page menu or open
207
208
  `/<id>/v/<n>`: the reader signed in as `ownerEmail` or any address in `admins` (case does not
208
- matter). Everyone else still sees "Version N · Updated <minute>", and a past version is a 404 to
209
- them. With neither configured, nobody does. `history: 'everyone'` in the config restores the old
210
- behaviour, where every reader who can open the page reads its history.
209
+ matter). A past version is a 404 to everyone else. With neither configured, nobody does.
210
+ `history: 'everyone'` in the config restores the old behaviour, where every reader who can open
211
+ the page reads its history.
212
+
213
+ **The version number goes with the history (0.41.0).** A page sent to someone reads as a message,
214
+ and "Version 9" on a message tells its reader about eight drafts. So whoever may not see past
215
+ versions sees only "Updated <minute>" under the summary; admins, and every reader under
216
+ `history: 'everyone'`, see "Version N · Updated <minute>". A pack's `Cover` is handed
217
+ `showVersion` alongside `version`, and draws the number itself only when it is true.
218
+
219
+ **One page can choose for itself (0.42.0).** `history: everyone` in a page's front matter shows
220
+ that page's readers the number, Version history and its past versions, on a host that keeps them
221
+ to admins; `history: admins` does the reverse on a host that shows everyone. Use `everyone` for a
222
+ document its readers follow from draft to draft (a spec, a plan, a paper under revision), and
223
+ leave the line out of anything that reads as a message to someone. It is content, like `toc:`: a
224
+ republish without the line goes back to the host's setting. A per-reader page serves no past
225
+ versions, so `history: everyone` is refused there.
211
226
 
212
227
  ```ts
213
228
  createArtifactRoutes({ ..., ownerEmail: 'owner@example.com', admins: ['editor@example.com'] })
@@ -254,6 +269,7 @@ summary: Three bets, what each costs, and what we stop doing to afford them.
254
269
  | `links` | `per-reader`: the page opens only through a link made for one reader (see Per-reader links). Sticky: a republish whose file leaves the line out changes nothing |
255
270
  | `media` | `private`: the page's body media is kept private and served through short-lived signed URLs. Needs `links: per-reader`. Sticky like `links` |
256
271
  | `comment_name` | `required`: on a per-reader page, a comment through a group link needs a typed first name, and each browser on a group link is its own reader (see Per-reader links). Needs `links: per-reader` |
272
+ | `history` | `everyone` or `admins`: who sees this page's version number and past versions, overriding the host's `history` for this one page (see Version history). Absent, the host decides. Refused as `everyone` on a per-reader page |
257
273
  | `change` | One line saying what this version changed (see Version history) |
258
274
  | `template` | `document` (the default, the read-along reader) or `broadcast` (one message card sent to several people; see Broadcasts) |
259
275
 
@@ -63,7 +63,15 @@ export type ArtifactMeta = {
63
63
  /** `comment_name: required` (0.40.0): on a per-reader page, a comment through a group link must
64
64
  * carry a typed first name, and a named link is never asked for one. Opt-in; content, like `toc:`. */
65
65
  commentName?: 'required';
66
+ /** `history: everyone | admins` (0.42.0): who sees this page's version number and past versions,
67
+ * overriding the host's `history` for this one page. `everyone` suits a document its readers
68
+ * follow from draft to draft; a page that reads as a message leaves the line out. Content, like
69
+ * `toc:`: a republish without the line goes back to the host's setting. */
70
+ history?: PageHistory;
66
71
  };
72
+ /** Who sees a page's version number and past versions. */
73
+ export type PageHistory = 'admins' | 'everyone';
74
+ export declare const PAGE_HISTORY: readonly PageHistory[];
67
75
  /** What a page is. A broadcast is its own kind, not a document with extra flags: it renders as a
68
76
  * message card, opens only through per-reader links, keeps its media private, has comments off
69
77
  * and no narration by default, and expires 90 days after publish unless kept. */
@@ -7,6 +7,7 @@ import { mergeChecklistState, mergeWidgetState, scanWidgets } from './widgets.js
7
7
  import { COMMENTS_MODES, mergeCommentsState } from './comments.js';
8
8
  import { THEME_MODES } from '../brand/theme.js';
9
9
  import { parseDefinitions } from './definitions.js';
10
+ export const PAGE_HISTORY = ['admins', 'everyone'];
10
11
  export const ARTIFACT_KINDS = ['document', 'broadcast'];
11
12
  /** Any media in a body: an image, a video or an audio clip, written as markdown image syntax. */
12
13
  const MEDIA_IN_BODY = /!\[[^\]]*\]\(\s*<?([^)\s>]+)/g;
@@ -19,7 +20,7 @@ const PRIVATE_REF_IN_BODY = /^private:/;
19
20
  export const PACK_NAME = /^[a-z0-9][a-z0-9-]{0,39}$/;
20
21
  /** The longest `to:` a page may carry: a name or two, never a sentence. */
21
22
  export const MAX_TO_CHARS = 60;
22
- const KNOWN = new Set(['title', 'summary', 'subtitle', 'to', 'template', 'audience', 'cover', 'id', 'voice', 'narration', 'timings', 'narrationHash', 'password', 'access', 'state', 'theme', 'pack', 'toc', 'definitions', 'change', 'comments', 'comments_visible', 'links', 'media', 'comment_name']);
23
+ const KNOWN = new Set(['title', 'summary', 'subtitle', 'to', 'template', 'audience', 'cover', 'id', 'voice', 'narration', 'timings', 'narrationHash', 'password', 'access', 'state', 'theme', 'pack', 'toc', 'definitions', 'change', 'comments', 'comments_visible', 'links', 'media', 'comment_name', 'history']);
23
24
  export function parseArtifactSource(text,
24
25
  /** The kind a file that leaves `template:` out is read as: the stored page's kind on a
25
26
  * republish, so a broadcast stays one (sticky, like `links:`). Default `document`. */
@@ -102,6 +103,11 @@ opts = {}) {
102
103
  return { ok: false, error: 'pack must be a pack name: lowercase letters, digits and dashes, up to 40 characters' };
103
104
  meta.pack = d.pack;
104
105
  }
106
+ if (d.history !== undefined && d.history !== null) {
107
+ if (!PAGE_HISTORY.includes(d.history))
108
+ return { ok: false, error: `history must be one of: ${PAGE_HISTORY.join(', ')}` };
109
+ meta.history = d.history;
110
+ }
105
111
  if (d.toc !== undefined) {
106
112
  if (typeof d.toc !== 'boolean')
107
113
  return { ok: false, error: 'toc must be true or false' };
@@ -150,6 +156,9 @@ opts = {}) {
150
156
  // People on one broadcast never see each other's comments, so a per-reader page cannot share them.
151
157
  if (meta.links === 'per-reader' && meta.commentsVisible === 'readers')
152
158
  return { ok: false, error: 'a per-reader page keeps each reader\'s comments to the owner: drop comments_visible: readers' };
159
+ // /<id>/v/<n> of a per-reader page is never served, so the history list would be dead links.
160
+ if (meta.history === 'everyone' && meta.links === 'per-reader')
161
+ return { ok: false, error: 'a per-reader page serves no past versions: drop history: everyone' };
153
162
  if (meta.commentName && meta.links !== 'per-reader')
154
163
  return { ok: false, error: 'comment_name: required needs links: per-reader' };
155
164
  // Widgets declare their own slots, so a notes block (and a checklist with send) is merged into state: here, before the
@@ -1,4 +1,4 @@
1
- export { parseArtifactSource, ARTIFACT_KINDS, type ArtifactMeta, type ArtifactKind } from './front-matter.js';
1
+ export { parseArtifactSource, ARTIFACT_KINDS, PAGE_HISTORY, type ArtifactMeta, type ArtifactKind, type PageHistory } from './front-matter.js';
2
2
  export { ArtifactMarkdown, artifactUrlTransform } from './render.js';
3
3
  export { narrationText, narrationTextFromMarkdown, normalizeWord } from './narration.js';
4
4
  export { createArtifactStore, newArtifactId, cleanNote, kindOf, listOfKind, MAX_CHANGE_NOTE_CHARS, type ArtifactListItem, type ArtifactStore, type ArtifactRecord, type SaveResult, type SaveInput, type VersionEntry, type VersionRecord } from './store.js';
@@ -1,4 +1,4 @@
1
- export { parseArtifactSource, ARTIFACT_KINDS } from './front-matter.js';
1
+ export { parseArtifactSource, ARTIFACT_KINDS, PAGE_HISTORY } from './front-matter.js';
2
2
  export { ArtifactMarkdown, artifactUrlTransform } from './render.js';
3
3
  export { narrationText, narrationTextFromMarkdown, normalizeWord } from './narration.js';
4
4
  export { createArtifactStore, newArtifactId, cleanNote, kindOf, listOfKind, MAX_CHANGE_NOTE_CHARS } from './store.js';
@@ -33,6 +33,8 @@ export type ArtifactRecord = {
33
33
  pack?: string;
34
34
  toc?: boolean;
35
35
  commentName?: 'required';
36
+ /** The page's own `history:` line; absent, the host's setting decides. */
37
+ history?: 'admins' | 'everyone';
36
38
  definitions?: Definition[];
37
39
  /** Who may leave shared comments; absent is off. Personal notes need nothing. */
38
40
  comments?: CommentsMode;
@@ -164,6 +164,7 @@ async function saveArtifact(col, input) {
164
164
  ...(input.meta.pack ? { pack: input.meta.pack } : {}),
165
165
  ...(input.meta.toc !== undefined ? { toc: input.meta.toc } : {}),
166
166
  ...(input.meta.commentName ? { commentName: input.meta.commentName } : {}),
167
+ ...(input.meta.history ? { history: input.meta.history } : {}),
167
168
  ...(input.meta.definitions?.length ? { definitions: input.meta.definitions } : {}),
168
169
  };
169
170
  if (input.id) {
@@ -217,6 +218,7 @@ async function saveArtifact(col, input) {
217
218
  ...(input.meta.pack ? {} : { pack: FieldValue.delete() }),
218
219
  ...(input.meta.toc !== undefined ? {} : { toc: FieldValue.delete() }),
219
220
  ...(input.meta.commentName ? {} : { commentName: FieldValue.delete() }),
221
+ ...(input.meta.history ? {} : { history: FieldValue.delete() }),
220
222
  ...(input.meta.definitions?.length ? {} : { definitions: FieldValue.delete() }),
221
223
  };
222
224
  // `voice: none` is the page declaring it has no audio. An update merges, so the recording it
@@ -84,6 +84,10 @@ export type CoverProps = {
84
84
  /** The version line and, for a signed-in reader, who they are signed in as. Unspoken. */
85
85
  meta: ReactNode;
86
86
  version: number;
87
+ /** Whether this reader may be shown the version number: true for whoever the host's `history`
88
+ * lets see past versions (admins by default), false for everyone else. A Cover that draws
89
+ * `version` itself draws it only when this is true; `meta` already follows it. */
90
+ showVersion: boolean;
87
91
  updatedAt: string;
88
92
  createdAt: string;
89
93
  };
@@ -80,8 +80,9 @@ export type ArtifactRoutesConfig = {
80
80
  /** Sign-in emails, besides `ownerEmail`, of the people who administer this host's pages. They
81
81
  * see a page's version history; with `history: 'admins'` (the default) nobody else does. */
82
82
  admins?: readonly string[];
83
- /** Who sees a page's past versions: the History control under the summary and every
84
- * `/<id>/v/<n>`. `'admins'` (default): only `ownerEmail` and `admins`, signed in; to everyone
83
+ /** Who sees a page's past versions and its version number: Version history in the page menu,
84
+ * every `/<id>/v/<n>`, and "Version N" on the line under the summary (0.41.0; to everyone else
85
+ * that line is only "Updated <minute>"). `'admins'` (default): only `ownerEmail` and `admins`, signed in; to everyone
85
86
  * else the History control is absent and a past version is not found, because an earlier draft
86
87
  * can hold what the current one was edited to remove. `'everyone'`: every reader who can open
87
88
  * the page, which was the only behaviour before 0.21.0. */
@@ -86,8 +86,14 @@ export function createArtifactRoutes(config) {
86
86
  const owner = config.owner ?? brand.name;
87
87
  const ownerEmail = config.ownerEmail?.trim().toLowerCase();
88
88
  const admins = new Set([ownerEmail, ...(config.admins ?? []).map((e) => e.trim().toLowerCase())].filter((e) => !!e));
89
- /** Whether this reader may see a page's past versions (config `history`). */
90
- const seesHistory = (reader) => config.history === 'everyone' || (!!reader && admins.has(reader.email.trim().toLowerCase()));
89
+ /** Whether this reader may see this page's version number and past versions: the page's own
90
+ * `history:` line when it has one, else the host's (config `history`). Admins always may. */
91
+ const seesHistory = (a, reader) => {
92
+ // A per-reader page serves no past versions, and `links:` is sticky host-side state a later
93
+ // file need not repeat, so the parser's refusal cannot catch every case: ignore the line here.
94
+ const page = a.history === 'everyone' && a.links === 'per-reader' ? undefined : a.history;
95
+ return (page ?? config.history) === 'everyone' || (!!reader && admins.has(reader.email.trim().toLowerCase()));
96
+ };
91
97
  const readerSecret = () => config.readerSecret?.();
92
98
  /** Who this reader is to this host's pages: the page menu's role. Owner and admins share a menu. */
93
99
  const roleOf = (reader) => {
@@ -230,7 +236,7 @@ export function createArtifactRoutes(config) {
230
236
  : a.password
231
237
  ? (admin ? { url: `${pageUrl(a.id)}?key=${encodeURIComponent(a.password)}`, title: a.title, note: 'The link carries the password.' } : null)
232
238
  : { url: pageUrl(a.id), title: a.title };
233
- return (_jsx(PageMenu, { role: role, name: name, shutOut: shutOut, share: share, comments: !!comments, signIn: !reader && signInOrigin ? signInUrl(signInOrigin, pageUrl(a.id)) : null, signOut: reader ? signOutUrl(a.id) : null, history: history && history.length > 1 && seesHistory(reader) && viewing ? { items: history, base: pagePath(a.id), viewing } : null, markdown: admin && source ? source : null, settings: admin ? settings ?? null : null }));
239
+ return (_jsx(PageMenu, { role: role, name: name, shutOut: shutOut, share: share, comments: !!comments, signIn: !reader && signInOrigin ? signInUrl(signInOrigin, pageUrl(a.id)) : null, signOut: reader ? signOutUrl(a.id) : null, history: history && history.length > 1 && seesHistory(a, reader) && viewing ? { items: history, base: pagePath(a.id), viewing } : null, markdown: admin && source ? source : null, settings: admin ? settings ?? null : null }));
234
240
  }
235
241
  /** The kicker over every title: the pack's line, then who the page is for. Never spoken: the
236
242
  * whole paragraph is data-nospeak, and narrationText has no `to` to read. */
@@ -408,9 +414,12 @@ export function createArtifactRoutes(config) {
408
414
  return store.history(id).catch(() => null);
409
415
  }
410
416
  /** "Version N · Updated <minute>": the line under the summary. The way into the history is the
411
- * page menu's Version history (0.30.0), for whoever `history` lets see it. */
412
- function VersionLine({ n, at }) {
413
- return (_jsxs("p", { "data-nospeak": true, className: "mt-4 text-xs opacity-60", children: [`Version ${n} · Updated `, _jsx(UpdatedTime, { iso: at })] }));
417
+ * page menu's Version history (0.30.0), for whoever `history` lets see it. The number is part of
418
+ * that history, so the same readers see it (0.41.0): to anyone else the line is only "Updated
419
+ * <minute>", because a page sent to someone reads as a message, and "Version 9" on a message
420
+ * tells its reader about eight drafts they were never meant to think about. */
421
+ function VersionLine({ n, at, numbered }) {
422
+ return (_jsxs("p", { "data-nospeak": true, className: "mt-4 text-xs opacity-60", children: [numbered ? `Version ${n} · Updated ` : 'Updated ', _jsx(UpdatedTime, { iso: at })] }));
414
423
  }
415
424
  /** The source of one version as published: its front matter's identifying lines, then the body,
416
425
  * so it pastes into a session and republishes onto the same page. Quoted as JSON strings, which
@@ -431,7 +440,7 @@ export function createArtifactRoutes(config) {
431
440
  async function VersionBody(a, n, { top, reader }) {
432
441
  const { pack, brand } = lookOf(a);
433
442
  // Past versions are for admins unless the host says otherwise: to anyone else none exists.
434
- if (!seesHistory(reader))
443
+ if (!seesHistory(a, reader))
435
444
  notFound();
436
445
  const v = store.version ? await store.version(a.id, n) : null;
437
446
  if (!v)
@@ -443,7 +452,7 @@ export function createArtifactRoutes(config) {
443
452
  const title = v.title ?? a.title;
444
453
  const summary = v.summary ?? a.summary;
445
454
  const toc = showToc(v.markdown, a.toc) ? tocOf(v.markdown) : [];
446
- return (_jsxs(_Fragment, { children: [_jsx(Menu, { a: a, reader: reader, history: history, viewing: n, settings: await settingsFor(a, reader), source: isAdmin(reader) ? sourceOf(a.id, { title, subtitle: v.subtitle, summary, to: v.to }, v.markdown) : null }), toc.length ? _jsx(TocRail, { items: toc }) : null, _jsx("div", { "data-nospeak": true, className: `mx-auto max-w-2xl px-6 ${top ? 'pt-20 sm:pt-24' : 'pt-8'}`, children: _jsxs("p", { "data-version-banner": true, role: "status", className: "rounded-lg border px-4 py-3 text-sm", style: { borderColor: brand.accent }, children: [`You are reading version ${n} of ${of}. `, _jsx("a", { href: pagePath(a.id), className: "underline underline-offset-4", style: { color: brand.accent }, children: "Read the current version" })] }) }), _jsx(Framed, { pack: pack, toc: toc.length ? _jsx(TocInline, { items: toc }) : null, cover: pack.Cover ? (_jsx(pack.Cover, { title: title, subtitle: v.subtitle, summary: summary, to: v.to, markdown: v.markdown, kicker: _jsx(Kicker, { to: v.to, brand: brand }), meta: _jsx(VersionLine, { n: n, at: v.at }), version: n, updatedAt: v.at, createdAt: a.createdAt })) : (_jsxs("div", { className: `mx-auto max-w-2xl px-6 ${top ? 'pt-10 sm:pt-12' : 'pt-10'} pb-8 text-center`, children: [_jsx(Kicker, { to: v.to, brand: brand }), _jsx("h1", { className: "text-4xl sm:text-5xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: title }), v.subtitle ? (_jsx("p", { className: "mx-auto mt-4 max-w-xl text-xl sm:text-2xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: v.subtitle })) : null, _jsx("p", { className: "mx-auto mt-6 max-w-xl text-lg italic opacity-80", children: summary }), _jsx(VersionLine, { n: n, at: v.at })] })), children: _jsx(ArtifactMarkdown, { markdown: v.markdown, simulator: config.simulatorEmbed ? { embed: config.simulatorEmbed } : undefined, messages: config.messagesEmbed ? { embed: config.messagesEmbed } : undefined, definitions: a.definitions, checklist: { artifactId: a.id, accent: brand.accent }, Section: pack.Section, figure: pack.figure }) })] }));
455
+ return (_jsxs(_Fragment, { children: [_jsx(Menu, { a: a, reader: reader, history: history, viewing: n, settings: await settingsFor(a, reader), source: isAdmin(reader) ? sourceOf(a.id, { title, subtitle: v.subtitle, summary, to: v.to }, v.markdown) : null }), toc.length ? _jsx(TocRail, { items: toc }) : null, _jsx("div", { "data-nospeak": true, className: `mx-auto max-w-2xl px-6 ${top ? 'pt-20 sm:pt-24' : 'pt-8'}`, children: _jsxs("p", { "data-version-banner": true, role: "status", className: "rounded-lg border px-4 py-3 text-sm", style: { borderColor: brand.accent }, children: [`You are reading version ${n} of ${of}. `, _jsx("a", { href: pagePath(a.id), className: "underline underline-offset-4", style: { color: brand.accent }, children: "Read the current version" })] }) }), _jsx(Framed, { pack: pack, toc: toc.length ? _jsx(TocInline, { items: toc }) : null, cover: pack.Cover ? (_jsx(pack.Cover, { title: title, subtitle: v.subtitle, summary: summary, to: v.to, markdown: v.markdown, kicker: _jsx(Kicker, { to: v.to, brand: brand }), meta: _jsx(VersionLine, { n: n, at: v.at, numbered: true }), version: n, showVersion: true, updatedAt: v.at, createdAt: a.createdAt })) : (_jsxs("div", { className: `mx-auto max-w-2xl px-6 ${top ? 'pt-10 sm:pt-12' : 'pt-10'} pb-8 text-center`, children: [_jsx(Kicker, { to: v.to, brand: brand }), _jsx("h1", { className: "text-4xl sm:text-5xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: title }), v.subtitle ? (_jsx("p", { className: "mx-auto mt-4 max-w-xl text-xl sm:text-2xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: v.subtitle })) : null, _jsx("p", { className: "mx-auto mt-6 max-w-xl text-lg italic opacity-80", children: summary }), _jsx(VersionLine, { n: n, at: v.at, numbered: true })] })), children: _jsx(ArtifactMarkdown, { markdown: v.markdown, simulator: config.simulatorEmbed ? { embed: config.simulatorEmbed } : undefined, messages: config.messagesEmbed ? { embed: config.messagesEmbed } : undefined, definitions: a.definitions, checklist: { artifactId: a.id, accent: brand.accent }, Section: pack.Section, figure: pack.figure }) })] }));
447
456
  }
448
457
  /** A broadcast: one message card. The sender's words at the top, then the media (a video with
449
458
  * sound and controls, photos, a voice note's player, link cards) in the order the words put
@@ -481,8 +490,8 @@ export function createArtifactRoutes(config) {
481
490
  const current = a.version ?? (a.versions?.length ?? 0) + 1;
482
491
  return (_jsxs(_Fragment, { children: [_jsx(Menu, { a: a, reader: reader, comments: mode !== 'off', history: history, viewing: current, settings: await settingsFor(a, reader), source: isAdmin(reader) ? sourceOf(a.id, a, a.markdown) : null }), toc.length ? _jsx(TocRail, { items: toc }) : null, _jsx("div", { id: "artifact-narration-root", children: await (async () => {
483
492
  const n = a.version ?? (a.versions?.length ?? 0) + 1;
484
- const meta = (_jsxs(_Fragment, { children: [_jsx(VersionLine, { n: n, at: a.updatedAt }), top && reader ? (_jsxs("p", { "data-nospeak": true, "data-reader-line": true, className: "mt-2 text-xs opacity-60", children: [`Signed in as ${reader.email} · `, _jsx("a", { href: switchUrl(a.id), className: "underline", children: "Use a different account" }), ' · ', _jsx("a", { href: signOutUrl(a.id), className: "underline", children: "Sign out" })] })) : null] }));
485
- const header = pack.Cover ? (_jsx(pack.Cover, { title: a.title, subtitle: a.subtitle, summary: a.summary, to: a.to, markdown: a.markdown, kicker: _jsx(Kicker, { to: a.to, brand: brand }), meta: meta, version: n, updatedAt: a.updatedAt, createdAt: a.createdAt })) : (_jsxs("div", { className: `mx-auto max-w-2xl px-6 ${top ? 'pt-24 sm:pt-28' : 'pt-12'} pb-8 text-center`, children: [_jsx(Kicker, { to: a.to, brand: brand }), _jsx("h1", { className: "text-4xl sm:text-5xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: a.title }), a.subtitle ? (_jsx("p", { className: "mx-auto mt-4 max-w-xl text-xl sm:text-2xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: a.subtitle })) : null, _jsx("p", { className: "mx-auto mt-6 max-w-xl text-lg italic opacity-80", children: a.summary }), meta] }));
493
+ const meta = (_jsxs(_Fragment, { children: [_jsx(VersionLine, { n: n, at: a.updatedAt, numbered: seesHistory(a, reader) }), top && reader ? (_jsxs("p", { "data-nospeak": true, "data-reader-line": true, className: "mt-2 text-xs opacity-60", children: [`Signed in as ${reader.email} · `, _jsx("a", { href: switchUrl(a.id), className: "underline", children: "Use a different account" }), ' · ', _jsx("a", { href: signOutUrl(a.id), className: "underline", children: "Sign out" })] })) : null] }));
494
+ const header = pack.Cover ? (_jsx(pack.Cover, { title: a.title, subtitle: a.subtitle, summary: a.summary, to: a.to, markdown: a.markdown, kicker: _jsx(Kicker, { to: a.to, brand: brand }), meta: meta, version: n, showVersion: seesHistory(a, reader), updatedAt: a.updatedAt, createdAt: a.createdAt })) : (_jsxs("div", { className: `mx-auto max-w-2xl px-6 ${top ? 'pt-24 sm:pt-28' : 'pt-12'} pb-8 text-center`, children: [_jsx(Kicker, { to: a.to, brand: brand }), _jsx("h1", { className: "text-4xl sm:text-5xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: a.title }), a.subtitle ? (_jsx("p", { className: "mx-auto mt-4 max-w-xl text-xl sm:text-2xl", style: { fontFamily: brand.type.display, color: brand.ink }, children: a.subtitle })) : null, _jsx("p", { className: "mx-auto mt-6 max-w-xl text-lg italic opacity-80", children: a.summary }), meta] }));
486
495
  return (_jsx(Framed, { pack: pack, toc: toc.length ? _jsx(TocInline, { items: toc }) : null, cover: _jsxs(_Fragment, { children: [perReader?.closesAt && !perReader.owner ? (_jsx(FeedbackWindow, { owner: owner, label: perReader.label, note: perReader.note, group: perReader.group, closesAt: perReader.closesAt })) : perReader?.note ? (_jsx("p", { "data-for-reader": true, className: "mx-auto mt-6 mb-0 max-w-2xl px-6 text-lg", children: `For ${perReader.label}: ${perReader.note}` })) : null, header, a.cover ? (_jsx("div", { className: "mx-auto max-w-2xl px-6 pb-8", children: _jsx("img", { src: a.cover, alt: "", className: "w-full rounded-xl border border-[color:var(--a-line)]" }) })) : null] }), children: _jsx(ArtifactMarkdown, { markdown: a.markdown, definitions: a.definitions, notes: config.state && a.state ? { artifactId: a.id, accent: brand.accent } : undefined,
487
496
  // Ticks are kept in the reader's browser on any host; Send needs one that keeps answers.
488
497
  checklist: { artifactId: a.id, send: !!config.state && !!a.state && Object.hasOwn(a.state.slots, CHECKLIST_SLOT), owner, accent: brand.accent }, Section: pack.Section, figure: pack.figure, simulator: config.simulatorEmbed ? { embed: config.simulatorEmbed } : undefined, messages: config.messagesEmbed ? { embed: config.messagesEmbed } : undefined }) }));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supersuit/artifacts",
3
- "version": "0.40.1",
3
+ "version": "0.42.0",
4
4
  "description": "Publish markdown as branded, read-aloud pages from a Next.js app: the artifacts store, the route factory, the read-along reader, brand packs, password and confidential pages, and share cards.",
5
5
  "license": "MIT",
6
6
  "type": "module",