@supersuit/artifacts 0.56.0 → 0.57.2

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,40 @@
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.57.2 (2026-10-26)
7
+
8
+ **0.57.1, published.** 0.57.1 was tagged but never reached npm: its publish run refused it, because
9
+ a second test (`src/routes/page-analytics-mount.test.tsx`) still expected the label as the PostHog
10
+ name. That test now asserts the identity carries no `name`. Everything in the 0.57.1 entry below
11
+ ships in this version.
12
+
13
+ ## 0.57.1 (2026-10-26, never published: see 0.57.2)
14
+
15
+ **Page analytics no longer send a per-reader link's label to PostHog.** A per-reader page identified its
16
+ reader as `recipient:<rid>` with `name` set to the label the publisher gave the link, which is the
17
+ person's name, so every reader's name sat in the analytics provider. The operator's ruling for
18
+ superlinks (2026-10-26): analytics gets the link's id, never who it is for.
19
+
20
+ - The identity is `{ distinctId: 'recipient:<rid>' }`, no `name`. A signed-in reader on a gated page is
21
+ unchanged.
22
+ - DETECTOR: a PostHog person whose distinct id starts `recipient:` carries a `name` property.
23
+ - REMEDY: update the host. Names already sent stay in PostHog until deleted there.
24
+ - Tests: src/reader/analytics-plan.test.ts (the per-reader identity is the id alone).
25
+
26
+ ## 0.57.0 (2026-10-26)
27
+
28
+ **`GET /api/artifacts/<id>` says what a link to the page lets its reader do.** A sender approves a
29
+ message before any reader's link is minted, so the only way to tell the operator, on the send card,
30
+ whether the person can forward the page, must sign in to comment, or has a link that expires, is the
31
+ page's own settings. The response adds `share: { links, forward, comments, expires }`.
32
+
33
+ - `links`: `per-reader` or `open`. `forward`: the page's `forward:` line, `off` when unset.
34
+ `comments`: the page's `comments:`, `off` when unset. `expires`: a per-reader page's expiry, or null.
35
+ - Never anything about a reader.
36
+ - DETECTOR: `GET /api/artifacts/<id>` with the publish key answers no `share` field.
37
+ - REMEDY: update the host.
38
+ - Tests: forward.test.tsx "GET /api/artifacts/<id> says what a link to the page lets its reader do".
39
+
6
40
  ## 0.56.0 (2026-10-26)
7
41
 
8
42
  **A reader can pass a per-reader page on, and the link they make says who made it and from which
package/README.md CHANGED
@@ -254,6 +254,11 @@ body as written, lists included, and is what an agent should read; `text` is the
254
254
  drops list items. Neither ever carries the page's password or anything about its readers. A page
255
255
  behind a sign-in door, a password or per-reader links is readable this way by whoever holds the key.
256
256
 
257
+ The answer also carries `share: { links, forward, comments, expires }` (0.57.0): `links` is
258
+ `per-reader` or `open`, `forward` the page's `forward:` (`off` when unset), `comments` its
259
+ `comments:` (`off` when unset), and `expires` the per-reader page's expiry or null. It is what a
260
+ sender reads to say, before a reader's link exists, what that link will let its reader do.
261
+
257
262
  ## Front matter
258
263
 
259
264
  ```markdown
@@ -1274,7 +1279,7 @@ Who is recorded, and as whom (`analyticsPlan`, `src/reader/analytics-plan.ts`):
1274
1279
  - Never the owner or an admin (signed in, previewing `?as=reader`, or on the owner's own per-reader
1275
1280
  link), never a link-preview fetcher, never a past version.
1276
1281
  - A gated page identifies the signed-in reader by email.
1277
- - A per-reader link identifies as `recipient:<rid>` with the label the publisher gave it.
1282
+ - A per-reader link identifies as `recipient:<rid>` and nothing more: never the label the publisher gave it, which names the person (0.57.1). The id-to-person map stays with the publisher, in the recipients store.
1278
1283
  - A public or password page stays anonymous.
1279
1284
  - Every page, whatever its kind, records replays with all text and all inputs masked
1280
1285
  (`RECORDING_MASK`), and autocapture collects no element text. There is no opt-out.
@@ -6,8 +6,8 @@
6
6
  // - The owner and the host's admins are never recorded, signed in or on their own per-reader link.
7
7
  // - A link-preview fetcher is never handed the script.
8
8
  // - A reader is identified only where the page already knows who they are: a gated page by the
9
- // signed-in email, a per-reader link by its opaque recipient id and the label the publisher gave
10
- // it (never a phone or an email, which the recipients store does not hold). A public or password
9
+ // signed-in email, a per-reader link by its opaque recipient id alone: never the label the
10
+ // publisher gave it, which names the person (2026-10-26), and never a phone or an email. A public or password
11
11
  // page stays anonymous, even when its reader happens to be signed in.
12
12
  export const DEFAULT_POSTHOG_HOST = 'https://us.i.posthog.com';
13
13
  export function analyticsPlan(input) {
@@ -20,7 +20,8 @@ export function analyticsPlan(input) {
20
20
  identity = { distinctId: email, email, ...(input.reader.name ? { name: input.reader.name } : {}) };
21
21
  }
22
22
  else if (input.kind === 'per-reader' && input.recipient?.rid) {
23
- identity = { distinctId: `recipient:${input.recipient.rid}`, name: input.recipient.label };
23
+ // The link's id alone: who it is for stays with the publisher (superlinks, 2026-10-26).
24
+ identity = { distinctId: `recipient:${input.recipient.rid}` };
24
25
  }
25
26
  return {
26
27
  key,
@@ -1,6 +1,7 @@
1
1
  import type { Metadata } from 'next';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
  import { type ArtifactRecord, type ArtifactStore, type VersionEntry } from '../artifacts/store.js';
4
+ import { type CommentsMode } from '../artifacts/comments.js';
4
5
  import type { StateStore } from '../artifacts/state-store.js';
5
6
  import { type ArtifactAssets } from '../artifacts/assets.js';
6
7
  import type { BrandPack } from '../brand/pack.js';
@@ -126,7 +127,7 @@ export type ArtifactRoutesConfig = {
126
127
  * autocapture, scroll depth on leave, and section_viewed / narration_* events. Without a key no
127
128
  * page loads any analytics script. Never records the owner or an admin, never a link-preview
128
129
  * fetcher, never a past version; identifies a reader only on a gated page (their email) or a
129
- * per-reader link (its recipient id and label). See reader/analytics-plan.ts. */
130
+ * per-reader link (its recipient id alone, never the label). See reader/analytics-plan.ts. */
130
131
  analytics?: AnalyticsConfig | null;
131
132
  };
132
133
  /** Ids are 8 chars from the safe alphabet; anything else is not a page and never reaches the store. */
@@ -259,6 +260,12 @@ export declare function createArtifactRoutes(config: ArtifactRoutesConfig): {
259
260
  markdown: string;
260
261
  version: number;
261
262
  updatedAt: string;
263
+ share: {
264
+ links: string;
265
+ forward: "signed-in" | "off" | "open";
266
+ comments: CommentsMode;
267
+ expires: string | null;
268
+ };
262
269
  to?: string | undefined;
263
270
  summary: string;
264
271
  subtitle?: string | undefined;
@@ -906,6 +906,14 @@ export function createArtifactRoutes(config) {
906
906
  text: narrationText({ title: a.title, subtitle: a.subtitle, summary: a.summary, markdown: a.markdown }),
907
907
  title: a.title, ...(a.subtitle ? { subtitle: a.subtitle } : {}), summary: a.summary, ...(a.to ? { to: a.to } : {}),
908
908
  markdown: a.markdown, version: a.version ?? 1, updatedAt: a.updatedAt,
909
+ // What a link to this page lets its reader do (0.57.0), so a sender can say so before the
910
+ // reader's link exists: the send card is approved before anything is minted.
911
+ share: {
912
+ links: a.links === 'per-reader' ? 'per-reader' : 'open',
913
+ forward: a.forward ?? 'off',
914
+ comments: a.comments ?? 'off',
915
+ expires: a.links === 'per-reader' && config.recipients ? (await config.recipients.get(id))?.expireAt ?? null : null,
916
+ },
909
917
  });
910
918
  }
911
919
  /** PUT /api/artifacts/<id>/assets/<name>: raw bytes in, public URL out. 16 MiB cap. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@supersuit/artifacts",
3
- "version": "0.56.0",
3
+ "version": "0.57.2",
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",