@supersuit/artifacts 0.53.0 → 0.55.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 +34 -0
- package/README.md +6 -0
- package/lib/reader/page-analytics.js +17 -4
- package/lib/routes/artifacts.d.ts +7 -0
- package/lib/routes/artifacts.js +9 -1
- package/package.json +1 -1
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.55.0 (2026-10-26)
|
|
7
|
+
|
|
8
|
+
**`GET /api/artifacts/<id>` now returns the page as written, not only its narration.** It answered
|
|
9
|
+
`{ id, text }`, and `text` is the narration, which drops every list item, so an agent reading a
|
|
10
|
+
prep sheet through the key got its headings ("Questions for him", "About him, first:") with the
|
|
11
|
+
questions missing.
|
|
12
|
+
|
|
13
|
+
- The response adds `title`, `subtitle` (when set), `summary`, `to` (when set), `markdown`,
|
|
14
|
+
`version` and `updatedAt`. `text` is unchanged, so existing callers keep working.
|
|
15
|
+
- Never the password, the access list or reader data.
|
|
16
|
+
- DETECTOR: `GET /api/artifacts/<id>` with the publish key answers no `markdown` field.
|
|
17
|
+
- REMEDY: update the host.
|
|
18
|
+
- Tests: `src/routes/artifacts.test.tsx` "GET returns the narration text and the markdown".
|
|
19
|
+
|
|
20
|
+
## 0.54.0 (2026-10-25)
|
|
21
|
+
|
|
22
|
+
**A page no longer records under a reader left on the browser by another page.** posthog-js keeps
|
|
23
|
+
the last identity it was given on an origin, and `PageAnalytics` never cleared it, so a browser that
|
|
24
|
+
opened one per-reader link sent every password or public page it opened afterward under that
|
|
25
|
+
reader's recipient id and label, and a per-reader page's init-time `$pageview` went out under the
|
|
26
|
+
previous reader before `identify()` switched it.
|
|
27
|
+
|
|
28
|
+
- `PageAnalytics` calls `posthog.reset()` when the browser is identified as anyone other than the
|
|
29
|
+
page's own reader, before `register` (reset clears registered properties). An anonymous browser
|
|
30
|
+
is not reset, so its first visit still joins the reader it becomes; the device id survives.
|
|
31
|
+
- `capture_pageview` is now `false`; the page captures `$pageview` itself once the reader is settled.
|
|
32
|
+
- DETECTOR: in PostHog, a `password` or `public` `page_kind` event whose `distinct_id` starts with
|
|
33
|
+
`recipient:`, or whose person has a `name`, came from a host on an older version.
|
|
34
|
+
- REMEDY: update the host. Events already recorded keep the leftover identity; a dashboard should
|
|
35
|
+
name a reader only by an id of the page's own kind (a recipient id on `per-reader`, an email on
|
|
36
|
+
`gated`) and count everything else by device.
|
|
37
|
+
- Tests: `page-analytics-identity.test.tsx` mounts the component against a stand-in posthog-js
|
|
38
|
+
holding a leftover identity and checks the call order on every page kind.
|
|
39
|
+
|
|
6
40
|
## 0.53.0 (2026-10-25)
|
|
7
41
|
|
|
8
42
|
**Every per-reader link can record who minted it, on whose authority, when, and the funnel step it
|
package/README.md
CHANGED
|
@@ -248,6 +248,12 @@ createArtifactRoutes({ ..., ownerEmail: 'owner@example.com', admins: ['editor@ex
|
|
|
248
248
|
- `GET /api/artifacts/<id>/versions` lists the history; `POST` it `{ "version": 2, "note": "..." }`
|
|
249
249
|
(or `"note": null`) to write a note onto a version after the fact.
|
|
250
250
|
|
|
251
|
+
**Reading a page as its owner.** `GET /api/artifacts/<id>` with the publish key answers
|
|
252
|
+
`{ id, title, subtitle?, summary, to?, markdown, version, updatedAt, text }`. `markdown` is the
|
|
253
|
+
body as written, lists included, and is what an agent should read; `text` is the narration, which
|
|
254
|
+
drops list items. Neither ever carries the page's password or anything about its readers. A page
|
|
255
|
+
behind a sign-in door, a password or per-reader links is readable this way by whoever holds the key.
|
|
256
|
+
|
|
251
257
|
## Front matter
|
|
252
258
|
|
|
253
259
|
```markdown
|
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
// than a gated one: the replay still shows where the reader went, how far and how fast, without the
|
|
12
12
|
// words. Autocapture is told not to collect element text for the same reason. There is no opt-out:
|
|
13
13
|
// the options come from posthogInitOptions, which a test holds to RECORDING_MASK for every kind.
|
|
14
|
+
//
|
|
15
|
+
// posthog-js keeps the last identity it was given on this origin, and a page that names nobody does
|
|
16
|
+
// not clear it. So a browser that opened one reader's per-reader link reported every page it opened
|
|
17
|
+
// after under that reader's id and label, and a per-reader page's init-time view went out under the
|
|
18
|
+
// previous reader before identify switched it. The page now drops an identity it was not given
|
|
19
|
+
// before it records anything, and captures its own page view once it has settled who the reader is.
|
|
14
20
|
import { useEffect } from 'react';
|
|
15
21
|
import { currentSection } from '../artifacts/toc-spy.js';
|
|
16
22
|
/** Sections read for less than this are passed over, not read. */
|
|
@@ -65,7 +71,8 @@ export function posthogInitOptions(plan) {
|
|
|
65
71
|
return {
|
|
66
72
|
api_host: plan.host,
|
|
67
73
|
person_profiles: 'identified_only',
|
|
68
|
-
|
|
74
|
+
// Captured by the page once it has settled who the reader is (see PageAnalytics).
|
|
75
|
+
capture_pageview: false,
|
|
69
76
|
capture_pageleave: true,
|
|
70
77
|
autocapture: true,
|
|
71
78
|
// Autocapture records a clicked element's text; on these pages that text is the page.
|
|
@@ -85,11 +92,17 @@ export function PageAnalytics({ plan, rootId = 'artifact-narration-root' }) {
|
|
|
85
92
|
if (cancelled)
|
|
86
93
|
return;
|
|
87
94
|
posthog.init(key, posthogInitOptions({ host }));
|
|
95
|
+
const who = identity ? JSON.parse(identity) : null;
|
|
96
|
+
// A reader left on this browser by another page is dropped first: reset also clears the
|
|
97
|
+
// registered properties, so it comes before register. An anonymous browser is left alone, so
|
|
98
|
+
// its first visit still joins the reader it becomes.
|
|
99
|
+
const left = posthog.get_property('$user_state') === 'identified' ? posthog.get_distinct_id() : null;
|
|
100
|
+
if (left && left !== who?.distinctId)
|
|
101
|
+
posthog.reset();
|
|
88
102
|
posthog.register({ artifact_id: pageId, page_kind: kind, ...(tenant ? { tenant } : {}) });
|
|
89
|
-
if (
|
|
90
|
-
const who = JSON.parse(identity);
|
|
103
|
+
if (who)
|
|
91
104
|
posthog.identify(who.distinctId, { ...(who.email ? { email: who.email } : {}), ...(who.name ? { name: who.name } : {}) });
|
|
92
|
-
|
|
105
|
+
posthog.capture('$pageview');
|
|
93
106
|
const capture = (e, p) => posthog.capture(e, p);
|
|
94
107
|
const root = document.getElementById(rootId);
|
|
95
108
|
const ids = root ? Array.from(root.querySelectorAll('h2[id], h3[id]'), (h) => h.id) : [];
|
|
@@ -251,8 +251,15 @@ export declare function createArtifactRoutes(config: ArtifactRoutesConfig): {
|
|
|
251
251
|
GET: (request: NextRequest, { params }: Params) => Promise<NextResponse<{
|
|
252
252
|
error: string;
|
|
253
253
|
}> | NextResponse<{
|
|
254
|
+
markdown: string;
|
|
255
|
+
version: number;
|
|
256
|
+
updatedAt: string;
|
|
257
|
+
to?: string | undefined;
|
|
258
|
+
summary: string;
|
|
259
|
+
subtitle?: string | undefined;
|
|
254
260
|
id: string;
|
|
255
261
|
text: string;
|
|
262
|
+
title: string;
|
|
256
263
|
}>>;
|
|
257
264
|
DELETE: (request: NextRequest, { params }: Params) => Promise<NextResponse<{
|
|
258
265
|
error: string;
|
package/lib/routes/artifacts.js
CHANGED
|
@@ -891,7 +891,15 @@ export function createArtifactRoutes(config) {
|
|
|
891
891
|
const a = await kept.get(id);
|
|
892
892
|
if (!a)
|
|
893
893
|
return NextResponse.json({ error: `no artifact with id ${id}` }, { status: 404 });
|
|
894
|
-
|
|
894
|
+
// `text` is the narration (lists and headings without their items read badly aloud, so it
|
|
895
|
+
// drops them); `markdown` is the page as written, which is what an agent reading the page
|
|
896
|
+
// needs. Never the password or any reader data.
|
|
897
|
+
return NextResponse.json({
|
|
898
|
+
id,
|
|
899
|
+
text: narrationText({ title: a.title, subtitle: a.subtitle, summary: a.summary, markdown: a.markdown }),
|
|
900
|
+
title: a.title, ...(a.subtitle ? { subtitle: a.subtitle } : {}), summary: a.summary, ...(a.to ? { to: a.to } : {}),
|
|
901
|
+
markdown: a.markdown, version: a.version ?? 1, updatedAt: a.updatedAt,
|
|
902
|
+
});
|
|
895
903
|
}
|
|
896
904
|
/** PUT /api/artifacts/<id>/assets/<name>: raw bytes in, public URL out. 16 MiB cap. */
|
|
897
905
|
async function PUT_ASSET(request, { params }) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supersuit/artifacts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.55.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",
|