@principal-ai/principal-view-core 0.28.5 → 0.28.7

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.
Files changed (41) hide show
  1. package/dist/auxiliary/AuxiliaryManifestValidator.d.ts +4 -1
  2. package/dist/auxiliary/AuxiliaryManifestValidator.d.ts.map +1 -1
  3. package/dist/auxiliary/AuxiliaryManifestValidator.js +12 -9
  4. package/dist/auxiliary/AuxiliaryManifestValidator.js.map +1 -1
  5. package/dist/events/EventsCanvasValidator.d.ts +3 -0
  6. package/dist/events/EventsCanvasValidator.d.ts.map +1 -1
  7. package/dist/events/EventsCanvasValidator.js +5 -4
  8. package/dist/events/EventsCanvasValidator.js.map +1 -1
  9. package/dist/index.d.ts +2 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +6 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/node.d.ts +4 -0
  14. package/dist/node.d.ts.map +1 -1
  15. package/dist/node.js +11 -2
  16. package/dist/node.js.map +1 -1
  17. package/dist/scopes/ScopesCanvasValidator.d.ts +3 -0
  18. package/dist/scopes/ScopesCanvasValidator.d.ts.map +1 -1
  19. package/dist/scopes/ScopesCanvasValidator.js +5 -4
  20. package/dist/scopes/ScopesCanvasValidator.js.map +1 -1
  21. package/dist/storage/topic-types.d.ts +189 -0
  22. package/dist/storage/topic-types.d.ts.map +1 -0
  23. package/dist/storage/topic-types.js +59 -0
  24. package/dist/storage/topic-types.js.map +1 -0
  25. package/dist/storage/topicStore.d.ts +130 -0
  26. package/dist/storage/topicStore.d.ts.map +1 -0
  27. package/dist/storage/topicStore.js +389 -0
  28. package/dist/storage/topicStore.js.map +1 -0
  29. package/package.json +1 -1
  30. package/src/auxiliary/AuxiliaryManifestValidator.test.ts +14 -13
  31. package/src/auxiliary/AuxiliaryManifestValidator.ts +12 -9
  32. package/src/browser-safety.test.ts +170 -0
  33. package/src/events/EventsCanvasValidator.test.ts +2 -1
  34. package/src/events/EventsCanvasValidator.ts +5 -4
  35. package/src/index.ts +13 -0
  36. package/src/node.ts +24 -0
  37. package/src/scopes/ScopesCanvasValidator.test.ts +6 -5
  38. package/src/scopes/ScopesCanvasValidator.ts +5 -4
  39. package/src/storage/topic-types.ts +220 -0
  40. package/src/storage/topicStore.test.ts +156 -0
  41. package/src/storage/topicStore.ts +481 -0
@@ -0,0 +1,189 @@
1
+ /**
2
+ * Canonical topic contract — the shapes every consumer of `~/.principal/topics`
3
+ * agrees on. Browser-safe (no `node:*`); the file-per-topic store that reads and
4
+ * writes these lives in `./node` (see `topicStore.ts`).
5
+ *
6
+ * A **topic** is a curated bundle of trails on one subject. Two distinct named
7
+ * shapes, split by id-namespace / ownership, with an explicit translation
8
+ * between them — NOT one shared shape. They share most field names, but `id`
9
+ * and `trailIds` live in different namespaces (local vs server), so passing one
10
+ * where the other is expected corrupts the trail foreign-keys.
11
+ *
12
+ * - {@link DraftTopic} — locally-authored, what desktop persists + edits.
13
+ * - {@link PublishedTopic} — server-authoritative, what the server returns and
14
+ * mobile reads.
15
+ * - {@link publishedFromDraft} — the Draft -> Published projection (one-way;
16
+ * there is no reverse hydration).
17
+ *
18
+ * The shapes mirror the over-the-wire form used by the sharing API (web-ade) so
19
+ * a published topic and a locally stored one stay interchangeable on read. All
20
+ * timestamps are ISO 8601 strings because this contract crosses the desktop/web
21
+ * boundary.
22
+ */
23
+ /**
24
+ * Status of a topic — a small structured axis plus optional human nuance.
25
+ *
26
+ * `state` is the machine-meaningful signal (drives badge color, sorting, and
27
+ * filtering). `label` is free-form text shown in place of the default per-state
28
+ * label. `waitingOn` describes an external blocker and is meaningful when
29
+ * `state` is `waiting`.
30
+ *
31
+ * The structured shape is deliberate: it lets automations check and resolve a
32
+ * blocker without parsing prose — e.g. flip `waiting` -> `paused` once `until`
33
+ * passes, or once a referenced PR merges.
34
+ */
35
+ export interface TopicStatus {
36
+ /**
37
+ * Structured lifecycle axis, ordered by a feature's "aliveness" from nascent
38
+ * to retired. Readers treat an absent OR unrecognized `state` as
39
+ * `new-thought`, so legacy topics and renamed values need no migration.
40
+ */
41
+ state: 'new-thought' | 'working' | 'paused' | 'waiting' | 'done-for-now' | 'deprecated' | 'abandoned';
42
+ /** Free-form text shown in place of the default label for the state. */
43
+ label?: string;
44
+ /** External blocker description. Meaningful when `state` is `waiting`. */
45
+ waitingOn?: {
46
+ /** Human description of the blocker, e.g. "design sign-off". */
47
+ note?: string;
48
+ /** ISO 8601 — when the hold is expected to lift. */
49
+ until?: string;
50
+ /** Structured pointer to the thing being waited on, for automations. */
51
+ ref?: {
52
+ kind: 'url' | 'pr' | 'issue' | 'topic' | 'trail';
53
+ /** The address/id of the referenced thing. */
54
+ value: string;
55
+ /** Optional human label for display. */
56
+ title?: string;
57
+ };
58
+ };
59
+ }
60
+ /**
61
+ * An image attached to a topic — primarily a screenshot dragged into the
62
+ * description. Bytes live on the topic (referenced from the description via an
63
+ * `asset://<id>` markdown link), not inlined into the description string. A
64
+ * render-time resolver swaps `asset://<id>` for `url` (preferred) or a data-URL
65
+ * built from `data`, keeping a local (data-only) and a published (url-bearing)
66
+ * topic interchangeable on read.
67
+ *
68
+ * Local-only on a {@link DraftTopic}: assets are uploaded/resolved at publish
69
+ * time, so a {@link PublishedTopic} carries the resolved `url`, not raw `data`.
70
+ */
71
+ export interface TopicAsset {
72
+ /** Content hash — free dedup and the stable `asset://` target. */
73
+ id: string;
74
+ /** MIME type, e.g. "image/png". */
75
+ mime: string;
76
+ /** Base64-encoded bytes. Present locally and on publish. */
77
+ data?: string;
78
+ /** Resolvable URL, when the bytes have been offloaded (e.g. to S3). */
79
+ url?: string;
80
+ /** Markdown alt text. */
81
+ alt?: string;
82
+ /** Origin metadata, for future re-capture / open-live affordances. */
83
+ source?: {
84
+ storyId?: string;
85
+ storybookUrl?: string;
86
+ };
87
+ }
88
+ /**
89
+ * GitHub identity of a topic's creator. Populated when a signed-in user
90
+ * authors a topic; left undefined for local-only topics created before
91
+ * sign-in. The server requires this on publish.
92
+ */
93
+ export interface TopicCreator {
94
+ githubId: number;
95
+ githubLogin: string;
96
+ }
97
+ /**
98
+ * Locally-authored topic — the shape desktop persists to
99
+ * `~/.principal/topics/<id>.json` and edits.
100
+ *
101
+ * Local id namespace. `description` / `createdBy` are optional (a Draft may be
102
+ * authored before sign-in). Carries local-only {@link TopicAsset}s
103
+ * (`asset://<id>` screenshots, raw `data`). Has NO `visibility` — that's a
104
+ * published concept.
105
+ *
106
+ * A Draft keeps existing after publish (it can be in a "published state"); its
107
+ * link to the server identity lives in the desktop's `topics-sync.json`
108
+ * sidecar, not in this shape.
109
+ */
110
+ export interface DraftTopic {
111
+ /** Unique identifier in the LOCAL id namespace. */
112
+ id: string;
113
+ /** Display title. */
114
+ title: string;
115
+ /** Optional markdown body. */
116
+ description?: string;
117
+ /** Ordered list of LOCAL trail ids — foreign keys into the local trail store. */
118
+ trailIds: string[];
119
+ /** ISO 8601. */
120
+ createdAt: string;
121
+ /** ISO 8601. */
122
+ updatedAt: string;
123
+ /** Creator identity; undefined for topics authored before sign-in. */
124
+ createdBy?: TopicCreator;
125
+ /** Optional workflow status. Absent reads as `new-thought`. */
126
+ status?: TopicStatus;
127
+ /** Local image attachments, referenced from the description via `asset://<id>`. */
128
+ assets?: TopicAsset[];
129
+ }
130
+ /**
131
+ * Server-authoritative topic — the shape the server returns and mobile reads.
132
+ *
133
+ * Remote id namespace. `description` / `createdBy` are required (publish is the
134
+ * validation gate). Carries `visibility`; assets have been resolved to `url`s.
135
+ *
136
+ * CAVEAT: "Published" means "has a server identity", NOT "public" — a
137
+ * PublishedTopic can be `visibility: 'private'`.
138
+ */
139
+ export interface PublishedTopic {
140
+ /** Unique identifier in the REMOTE (server) id namespace. */
141
+ id: string;
142
+ /** Display title. */
143
+ title: string;
144
+ /** Markdown body — required on the server. */
145
+ description: string;
146
+ /** Ordered list of REMOTE trail ids. */
147
+ trailIds: string[];
148
+ /** ISO 8601. */
149
+ createdAt: string;
150
+ /** ISO 8601. */
151
+ updatedAt: string;
152
+ /** Creator identity — required on the server. */
153
+ createdBy: TopicCreator;
154
+ /** Optional workflow status, travels with the topic when published. */
155
+ status?: TopicStatus;
156
+ /** Server visibility. "private" is still a PublishedTopic. */
157
+ visibility: 'private' | 'public';
158
+ }
159
+ /**
160
+ * Inputs the {@link DraftTopic} cannot supply on its own — the namespace
161
+ * crossing and the fields publish must enforce. The caller resolves these
162
+ * before projecting:
163
+ * - `trailIds`: LOCAL trail ids remapped to REMOTE ids (web-ade references
164
+ * trails by their server id; see desktop's `resolveRemoteTrailIds`).
165
+ * - `createdBy`: required on the server; supplied here if the draft lacks it.
166
+ * - `visibility`: a published concept the draft never carries.
167
+ */
168
+ export interface PublishProjection {
169
+ trailIds: string[];
170
+ createdBy: TopicCreator;
171
+ visibility: 'private' | 'public';
172
+ }
173
+ /**
174
+ * Project a {@link DraftTopic} into a {@link PublishedTopic} — the one-way
175
+ * Draft -> Published translation. It does NOT perform any I/O (no id minting,
176
+ * no asset upload, no network): the caller resolves the namespace-crossing and
177
+ * required fields into {@link PublishProjection} first (remapping trail ids,
178
+ * uploading assets, choosing visibility), and this enforces the shape.
179
+ *
180
+ * One-way by design: there is no reverse `PublishedTopic -> DraftTopic`
181
+ * hydration. A published topic's edits write through to the server; the local
182
+ * Draft is reconciled from the server's response field-by-field, never by
183
+ * rebuilding a Draft from a Published shape.
184
+ *
185
+ * Throws if `description` is empty — publish is also the validation gate, and
186
+ * the server rejects a description-less topic.
187
+ */
188
+ export declare function publishedFromDraft(draft: DraftTopic, projection: PublishProjection): PublishedTopic;
189
+ //# sourceMappingURL=topic-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic-types.d.ts","sourceRoot":"","sources":["../../src/storage/topic-types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ /**
3
+ * Canonical topic contract — the shapes every consumer of `~/.principal/topics`
4
+ * agrees on. Browser-safe (no `node:*`); the file-per-topic store that reads and
5
+ * writes these lives in `./node` (see `topicStore.ts`).
6
+ *
7
+ * A **topic** is a curated bundle of trails on one subject. Two distinct named
8
+ * shapes, split by id-namespace / ownership, with an explicit translation
9
+ * between them — NOT one shared shape. They share most field names, but `id`
10
+ * and `trailIds` live in different namespaces (local vs server), so passing one
11
+ * where the other is expected corrupts the trail foreign-keys.
12
+ *
13
+ * - {@link DraftTopic} — locally-authored, what desktop persists + edits.
14
+ * - {@link PublishedTopic} — server-authoritative, what the server returns and
15
+ * mobile reads.
16
+ * - {@link publishedFromDraft} — the Draft -> Published projection (one-way;
17
+ * there is no reverse hydration).
18
+ *
19
+ * The shapes mirror the over-the-wire form used by the sharing API (web-ade) so
20
+ * a published topic and a locally stored one stay interchangeable on read. All
21
+ * timestamps are ISO 8601 strings because this contract crosses the desktop/web
22
+ * boundary.
23
+ */
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.publishedFromDraft = void 0;
26
+ /**
27
+ * Project a {@link DraftTopic} into a {@link PublishedTopic} — the one-way
28
+ * Draft -> Published translation. It does NOT perform any I/O (no id minting,
29
+ * no asset upload, no network): the caller resolves the namespace-crossing and
30
+ * required fields into {@link PublishProjection} first (remapping trail ids,
31
+ * uploading assets, choosing visibility), and this enforces the shape.
32
+ *
33
+ * One-way by design: there is no reverse `PublishedTopic -> DraftTopic`
34
+ * hydration. A published topic's edits write through to the server; the local
35
+ * Draft is reconciled from the server's response field-by-field, never by
36
+ * rebuilding a Draft from a Published shape.
37
+ *
38
+ * Throws if `description` is empty — publish is also the validation gate, and
39
+ * the server rejects a description-less topic.
40
+ */
41
+ function publishedFromDraft(draft, projection) {
42
+ const description = (draft.description ?? '').trim();
43
+ if (description.length === 0) {
44
+ throw new Error(`Topic ${draft.id} has no description; a description is required to publish.`);
45
+ }
46
+ return {
47
+ id: draft.id,
48
+ title: draft.title,
49
+ description,
50
+ trailIds: projection.trailIds,
51
+ createdAt: draft.createdAt,
52
+ updatedAt: draft.updatedAt,
53
+ createdBy: projection.createdBy,
54
+ ...(draft.status !== undefined ? { status: draft.status } : {}),
55
+ visibility: projection.visibility,
56
+ };
57
+ }
58
+ exports.publishedFromDraft = publishedFromDraft;
59
+ //# sourceMappingURL=topic-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic-types.js","sourceRoot":"","sources":["../../src/storage/topic-types.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;;;AAkKH;;;;;;;;;;;;;;GAcG;AACH,SAAgB,kBAAkB,CAChC,KAAiB,EACjB,UAA6B;IAE7B,MAAM,WAAW,GAAG,CAAC,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;IACrD,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE;QAC5B,MAAM,IAAI,KAAK,CACb,SAAS,KAAK,CAAC,EAAE,4DAA4D,CAC9E,CAAC;KACH;IACD,OAAO;QACL,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,WAAW;QACX,QAAQ,EAAE,UAAU,CAAC,QAAQ;QAC7B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,KAAK,CAAC,SAAS;QAC1B,SAAS,EAAE,UAAU,CAAC,SAAS;QAC/B,GAAG,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/D,UAAU,EAAE,UAAU,CAAC,UAAU;KAClC,CAAC;AACJ,CAAC;AArBD,gDAqBC"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * File-per-topic store under `~/.principal/topics/`.
3
+ *
4
+ * Layout (mirrors the trail store at `~/.principal/trails/`, so topics become
5
+ * locally greppable and an agent can read one directly):
6
+ *
7
+ * ~/.principal/topics/
8
+ * _index.json private, rebuildable manifest (entries[])
9
+ * <id>.json one pretty-printed DraftTopic per file
10
+ *
11
+ * Topics are the *least* repo-bound artifact (a bundle of trails spanning
12
+ * repos), so — unlike trails, which bucket by repo Purl — they are stored flat
13
+ * by id. No purl, no buckets, no `node:os`/Purl dependency beyond `homedir()`.
14
+ *
15
+ * The `_index.json` manifest is store-private and rebuildable: it is rebuilt by
16
+ * scanning the directory whenever it is missing or unparseable. It exists only
17
+ * to make `getTopics()` / `list()` cheap (no per-file read for listing).
18
+ *
19
+ * There is intentionally NO eviction cap. The trail store caps trails per repo
20
+ * (`PER_REPO_CAP`); topics are few, user-curated, and must never be silently
21
+ * dropped, so the cap is deliberately not carried over.
22
+ *
23
+ * Migration from the legacy single-blob `~/.alexandria/topics.json` is explicit
24
+ * (`migrateFromLegacyBlob`) — it is never run automatically on load; the desktop
25
+ * triggers it from a Settings action.
26
+ */
27
+ import type { DraftTopic, TopicStatus } from './topic-types';
28
+ /** Root of the global, cross-repo principal narration store. */
29
+ export declare const PRINCIPAL_DIR: string;
30
+ /** File-per-topic directory. */
31
+ export declare const TOPICS_DIR: string;
32
+ /** Legacy single-blob path the migration reads from. */
33
+ export declare const LEGACY_TOPICS_BLOB: string;
34
+ /**
35
+ * Cheap-to-list projection of a topic, held in the private manifest so listing
36
+ * never reads every file. Everything here is derivable from the topic file, so
37
+ * the manifest can always be rebuilt by scanning.
38
+ */
39
+ export interface TopicIndexEntry {
40
+ id: string;
41
+ title: string;
42
+ descriptionPreview: string;
43
+ trailCount: number;
44
+ /** `status.state` lifted out for filtering/sorting without a file read. */
45
+ state?: TopicStatus['state'];
46
+ createdAt: string;
47
+ updatedAt: string;
48
+ createdBy?: {
49
+ githubId: number;
50
+ githubLogin: string;
51
+ };
52
+ hasAssets: boolean;
53
+ sizeBytes: number;
54
+ /** File name relative to `TOPICS_DIR` (e.g. `topic-123.json`). */
55
+ fileName: string;
56
+ }
57
+ export interface MigrationResult {
58
+ /** Number of topics written to the file-per-topic store. */
59
+ migrated: number;
60
+ /** Number of legacy topics skipped (missing id, write failure). */
61
+ skipped: number;
62
+ /** Absolute path of the `.bak` the legacy blob was renamed to, if any. */
63
+ backupPath?: string;
64
+ /** True when there was no legacy blob to migrate. */
65
+ noLegacyBlob: boolean;
66
+ }
67
+ /**
68
+ * Fields a caller may set when updating a topic. `id`, `createdAt`, and
69
+ * `trailIds` are not updatable here — use the trail-membership methods for
70
+ * `trailIds`, and `id`/`createdAt` are immutable.
71
+ */
72
+ export type TopicUpdate = Partial<Pick<DraftTopic, 'title' | 'description' | 'status' | 'createdBy' | 'assets'>>;
73
+ /**
74
+ * Fields a caller may set when creating a topic. `id`, `createdAt`,
75
+ * `updatedAt` are filled in when omitted; passing `id` lets a caller control
76
+ * it (e.g. round-tripping a server id).
77
+ */
78
+ export type TopicCreate = Omit<DraftTopic, 'createdAt' | 'updatedAt'> & {
79
+ id?: string;
80
+ createdAt?: string;
81
+ updatedAt?: string;
82
+ };
83
+ export declare class TopicStore {
84
+ private readonly baseDir;
85
+ private readonly indexPath;
86
+ private readonly legacyBlobPath;
87
+ private index;
88
+ private writeQueue;
89
+ /**
90
+ * @param baseDir file-per-topic directory (defaults to `TOPICS_DIR`)
91
+ * @param legacyBlobPath legacy blob the migration reads (defaults to
92
+ * `LEGACY_TOPICS_BLOB`); injectable for tests.
93
+ */
94
+ constructor(baseDir?: string, legacyBlobPath?: string);
95
+ getTopics(): Promise<DraftTopic[]>;
96
+ getTopic(id: string): Promise<DraftTopic | null>;
97
+ /** Cheap listing straight off the manifest — no per-topic file read. */
98
+ list(): Promise<TopicIndexEntry[]>;
99
+ createTopic(input: TopicCreate): Promise<DraftTopic>;
100
+ updateTopic(id: string, updates: TopicUpdate): Promise<DraftTopic>;
101
+ deleteTopic(id: string): Promise<boolean>;
102
+ addTrailToTopic(topicId: string, trailId: string): Promise<DraftTopic>;
103
+ removeTrailFromTopic(topicId: string, trailId: string): Promise<DraftTopic>;
104
+ reorderTopicTrails(topicId: string, trailIds: string[]): Promise<DraftTopic>;
105
+ getTopicsForTrail(trailId: string): Promise<DraftTopic[]>;
106
+ /**
107
+ * One-shot migration from the legacy single-blob `~/.alexandria/topics.json`
108
+ * to file-per-topic. Reads the blob's `topics[]`, writes each as
109
+ * `<id>.json`, rebuilds the index, then renames the blob to `<blob>.bak` so
110
+ * a re-run is a no-op (mirrors the trail store's `migrateLegacyIfPresent`).
111
+ *
112
+ * Explicit by design — the desktop calls this from a Settings action, never
113
+ * on load. Idempotent: once the blob is `.bak`'d, subsequent calls report
114
+ * `noLegacyBlob`. An existing topic id is overwritten (the blob wins on a
115
+ * first migration), so re-importing after edits is the caller's decision.
116
+ */
117
+ migrateFromLegacyBlob(): Promise<MigrationResult>;
118
+ private getIndex;
119
+ private requireTopic;
120
+ /** Write a topic file and upsert its index entry. Does not generate ids. */
121
+ private writeTopic;
122
+ private readTopic;
123
+ private unlinkRelative;
124
+ private pathExists;
125
+ private loadIndex;
126
+ /** Rebuild the manifest by scanning every `<id>.json` in the directory. */
127
+ private rebuildIndex;
128
+ private persistIndex;
129
+ }
130
+ //# sourceMappingURL=topicStore.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topicStore.d.ts","sourceRoot":"","sources":["../../src/storage/topicStore.ts"],"names":[],"mappings":"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"}