@principal-ai/subsystems-core 0.51.1 → 0.51.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/README.md +9 -2
- package/dist/storage/topic-reference-links.d.ts +3 -3
- package/dist/storage/topic-reference-links.d.ts.map +1 -1
- package/dist/storage/topic-reference-links.js +23 -8
- package/dist/storage/topic-reference-links.js.map +1 -1
- package/dist/storage/topic-types.d.ts +5 -1
- package/dist/storage/topic-types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/storage/topic-reference-links.test.ts +7 -1
- package/src/storage/topic-reference-links.ts +24 -8
- package/src/storage/topic-types.ts +3 -1
- package/src/storage/topicStore.test.ts +1 -0
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@ Portable document schema: `schemas/subsystem-model.schema.json`
|
|
|
16
16
|
|
|
17
17
|
## Topic references
|
|
18
18
|
|
|
19
|
-
`DraftTopic.references` attaches local topics
|
|
19
|
+
`DraftTopic.references` attaches local topics, C4 sketches, and drawings by ID:
|
|
20
20
|
|
|
21
21
|
```ts
|
|
22
22
|
import {
|
|
@@ -29,14 +29,21 @@ const reference: TopicReference = { kind: 'c4-sketch', sketchId: 'c4s-123-abc' }
|
|
|
29
29
|
formatTopicReferenceLink(reference); // principal://c4-sketch/c4s-123-abc
|
|
30
30
|
parseTopicReferenceLink('principal://topic/topic-123-abc');
|
|
31
31
|
// { kind: 'topic', topicId: 'topic-123-abc' }
|
|
32
|
+
parseTopicReferenceLink('principal://drawing/0b6f7c1e');
|
|
33
|
+
// { kind: 'drawing', drawingId: '0b6f7c1e' }
|
|
32
34
|
```
|
|
33
35
|
|
|
36
|
+
A drawing ID is the file name, without extension, of a `.excalidraw` file in the
|
|
37
|
+
host's drawings folder (the desktop app uses `~/.alexandria/drawings/`). Titles
|
|
38
|
+
come from the drawing file's `appState.name` and are resolved by the host.
|
|
39
|
+
|
|
34
40
|
Create/update accepts `references`; updates replace the whole array (`[]` clears
|
|
35
41
|
it). Existing topics may omit the field. References are directional and local-only.
|
|
36
42
|
Targets stay separate artifacts; deleting a target leaves a missing reference,
|
|
37
43
|
not a cascading deletion. Existing `subsystemModels` associations are unchanged.
|
|
38
44
|
|
|
39
|
-
Markdown links such as `[Architecture](principal://c4-sketch/c4s-123-abc)
|
|
45
|
+
Markdown links such as `[Architecture](principal://c4-sketch/c4s-123-abc)`,
|
|
46
|
+
`[Whiteboard](principal://drawing/0b6f7c1e)`, and
|
|
40
47
|
`[Related](principal://topic/topic-123-abc)` use the same target shapes but are
|
|
41
48
|
independent of attachments. Helpers only format/parse hrefs; hosts must allow the
|
|
42
49
|
`principal` protocol in Markdown sanitizers and handle resolution, navigation,
|
|
@@ -2,9 +2,9 @@ import type { TopicReference } from './topic-types';
|
|
|
2
2
|
/** Format a Markdown-safe href. Does not attach or resolve the target. */
|
|
3
3
|
export declare function formatTopicReferenceLink(reference: TopicReference): string;
|
|
4
4
|
/**
|
|
5
|
-
* Parse a local topic/sketch href without I/O. Unknown kinds, malformed
|
|
6
|
-
* extra path segments, queries, and fragments return null. Target
|
|
7
|
-
* navigation belong to the host. Parsing never creates an attachment.
|
|
5
|
+
* Parse a local topic/sketch/drawing href without I/O. Unknown kinds, malformed
|
|
6
|
+
* escapes, extra path segments, queries, and fragments return null. Target
|
|
7
|
+
* existence and navigation belong to the host. Parsing never creates an attachment.
|
|
8
8
|
*/
|
|
9
9
|
export declare function parseTopicReferenceLink(href: string): TopicReference | null;
|
|
10
10
|
//# sourceMappingURL=topic-reference-links.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topic-reference-links.d.ts","sourceRoot":"","sources":["../../src/storage/topic-reference-links.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"topic-reference-links.d.ts","sourceRoot":"","sources":["../../src/storage/topic-reference-links.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAmBpD,0EAA0E;AAC1E,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,cAAc,GAAG,MAAM,CAQ1E;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAkB3E"}
|
|
@@ -6,9 +6,19 @@ exports.parseTopicReferenceLink = exports.formatTopicReferenceLink = void 0;
|
|
|
6
6
|
function isReferenceId(id) {
|
|
7
7
|
return id.length > 0 && id !== '.' && id !== '..' && !/[/\\\s\u0000-\u001f\u007f]/.test(id);
|
|
8
8
|
}
|
|
9
|
+
function referenceId(reference) {
|
|
10
|
+
switch (reference.kind) {
|
|
11
|
+
case 'topic':
|
|
12
|
+
return reference.topicId;
|
|
13
|
+
case 'c4-sketch':
|
|
14
|
+
return reference.sketchId;
|
|
15
|
+
case 'drawing':
|
|
16
|
+
return reference.drawingId;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
9
19
|
/** Format a Markdown-safe href. Does not attach or resolve the target. */
|
|
10
20
|
function formatTopicReferenceLink(reference) {
|
|
11
|
-
const id = reference
|
|
21
|
+
const id = referenceId(reference);
|
|
12
22
|
if (!isReferenceId(id))
|
|
13
23
|
throw new Error('Invalid topic reference id');
|
|
14
24
|
const encoded = encodeURIComponent(id).replace(/[!'()*]/g, (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`);
|
|
@@ -16,12 +26,12 @@ function formatTopicReferenceLink(reference) {
|
|
|
16
26
|
}
|
|
17
27
|
exports.formatTopicReferenceLink = formatTopicReferenceLink;
|
|
18
28
|
/**
|
|
19
|
-
* Parse a local topic/sketch href without I/O. Unknown kinds, malformed
|
|
20
|
-
* extra path segments, queries, and fragments return null. Target
|
|
21
|
-
* navigation belong to the host. Parsing never creates an attachment.
|
|
29
|
+
* Parse a local topic/sketch/drawing href without I/O. Unknown kinds, malformed
|
|
30
|
+
* escapes, extra path segments, queries, and fragments return null. Target
|
|
31
|
+
* existence and navigation belong to the host. Parsing never creates an attachment.
|
|
22
32
|
*/
|
|
23
33
|
function parseTopicReferenceLink(href) {
|
|
24
|
-
const match = /^principal:\/\/(topic|c4-sketch)\/([^/?#]+)$/.exec(href);
|
|
34
|
+
const match = /^principal:\/\/(topic|c4-sketch|drawing)\/([^/?#]+)$/.exec(href);
|
|
25
35
|
if (!match)
|
|
26
36
|
return null;
|
|
27
37
|
let id;
|
|
@@ -33,9 +43,14 @@ function parseTopicReferenceLink(href) {
|
|
|
33
43
|
}
|
|
34
44
|
if (!isReferenceId(id))
|
|
35
45
|
return null;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
switch (match[1]) {
|
|
47
|
+
case 'topic':
|
|
48
|
+
return { kind: 'topic', topicId: id };
|
|
49
|
+
case 'c4-sketch':
|
|
50
|
+
return { kind: 'c4-sketch', sketchId: id };
|
|
51
|
+
default:
|
|
52
|
+
return { kind: 'drawing', drawingId: id };
|
|
53
|
+
}
|
|
39
54
|
}
|
|
40
55
|
exports.parseTopicReferenceLink = parseTopicReferenceLink;
|
|
41
56
|
//# sourceMappingURL=topic-reference-links.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topic-reference-links.js","sourceRoot":"","sources":["../../src/storage/topic-reference-links.ts"],"names":[],"mappings":";;;AAEA,0EAA0E;AAC1E,oEAAoE;AACpE,SAAS,aAAa,CAAC,EAAU;IAC/B,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC9F,CAAC;AAED,
|
|
1
|
+
{"version":3,"file":"topic-reference-links.js","sourceRoot":"","sources":["../../src/storage/topic-reference-links.ts"],"names":[],"mappings":";;;AAEA,0EAA0E;AAC1E,oEAAoE;AACpE,SAAS,aAAa,CAAC,EAAU;IAC/B,OAAO,EAAE,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,IAAI,IAAI,CAAC,4BAA4B,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AAC9F,CAAC;AAED,SAAS,WAAW,CAAC,SAAyB;IAC5C,QAAQ,SAAS,CAAC,IAAI,EAAE;QACtB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC,OAAO,CAAC;QAC3B,KAAK,WAAW;YACd,OAAO,SAAS,CAAC,QAAQ,CAAC;QAC5B,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC,SAAS,CAAC;KAC9B;AACH,CAAC;AAED,0EAA0E;AAC1E,SAAgB,wBAAwB,CAAC,SAAyB;IAChE,MAAM,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC;IAClC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IACtE,MAAM,OAAO,GAAG,kBAAkB,CAAC,EAAE,CAAC,CAAC,OAAO,CAC5C,UAAU,EACV,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,SAAS,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,EAAE,CACxE,CAAC;IACF,OAAO,eAAe,SAAS,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC;AACpD,CAAC;AARD,4DAQC;AAED;;;;GAIG;AACH,SAAgB,uBAAuB,CAAC,IAAY;IAClD,MAAM,KAAK,GAAG,sDAAsD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChF,IAAI,CAAC,KAAK;QAAE,OAAO,IAAI,CAAC;IACxB,IAAI,EAAU,CAAC;IACf,IAAI;QACF,EAAE,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KACnC;IAAC,MAAM;QACN,OAAO,IAAI,CAAC;KACb;IACD,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,QAAQ,KAAK,CAAC,CAAC,CAAC,EAAE;QAChB,KAAK,OAAO;YACV,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC;QACxC,KAAK,WAAW;YACd,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;QAC7C;YACE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,CAAC;KAC7C;AACH,CAAC;AAlBD,0DAkBC"}
|
|
@@ -59,10 +59,14 @@ export interface TopicSubsystemModelRef {
|
|
|
59
59
|
* A directional, local-only reference owned by a topic. Targets remain separate
|
|
60
60
|
* artifacts; their contents and titles are resolved by the host, not copied here.
|
|
61
61
|
* Referencing a C4 sketch does not make its authored hypothesis evidence.
|
|
62
|
+
* A drawing ID is the `.excalidraw` file stem under the host's drawings folder.
|
|
62
63
|
*/
|
|
63
64
|
export type TopicReference = {
|
|
64
65
|
kind: 'c4-sketch';
|
|
65
66
|
sketchId: string;
|
|
67
|
+
} | {
|
|
68
|
+
kind: 'drawing';
|
|
69
|
+
drawingId: string;
|
|
66
70
|
} | {
|
|
67
71
|
kind: 'topic';
|
|
68
72
|
topicId: string;
|
|
@@ -104,7 +108,7 @@ export interface DraftTopic {
|
|
|
104
108
|
*/
|
|
105
109
|
subsystemModels?: TopicSubsystemModelRef[];
|
|
106
110
|
/**
|
|
107
|
-
* Attached topics
|
|
111
|
+
* Attached topics, C4 sketches, and drawings. Whole-array replacement on update; `[]`
|
|
108
112
|
* clears attachments. Independent of links in `description`: neither edits
|
|
109
113
|
* the other. Hosts validate target existence and reject self-references.
|
|
110
114
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"topic-types.d.ts","sourceRoot":"","sources":["../../src/storage/topic-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED
|
|
1
|
+
{"version":3,"file":"topic-types.d.ts","sourceRoot":"","sources":["../../src/storage/topic-types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,kEAAkE;IAClE,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACtD;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAsB;IACrC,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACvC;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GACtC;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEvC;;;;;;;GAOG;AACH,MAAM,WAAW,UAAU;IACzB,mDAAmD;IACnD,EAAE,EAAE,MAAM,CAAC;IACX,qBAAqB;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,sEAAsE;IACtE,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB,mFAAmF;IACnF,MAAM,CAAC,EAAE,UAAU,EAAE,CAAC;IACtB;;;OAGG;IACH,eAAe,CAAC,EAAE,sBAAsB,EAAE,CAAC;IAC3C;;;;OAIG;IACH,UAAU,CAAC,EAAE,cAAc,EAAE,CAAC;CAC/B"}
|
package/package.json
CHANGED
|
@@ -3,17 +3,20 @@ import { formatTopicReferenceLink, parseTopicReferenceLink } from './topic-refer
|
|
|
3
3
|
import type { TopicReference } from './topic-types';
|
|
4
4
|
|
|
5
5
|
describe('topic reference links', () => {
|
|
6
|
-
test('formats topic
|
|
6
|
+
test('formats topic, C4 sketch, and drawing links', () => {
|
|
7
7
|
expect(formatTopicReferenceLink({ kind: 'topic', topicId: 'topic-123-abc' }))
|
|
8
8
|
.toBe('principal://topic/topic-123-abc');
|
|
9
9
|
expect(formatTopicReferenceLink({ kind: 'c4-sketch', sketchId: 'c4s-123-abc' }))
|
|
10
10
|
.toBe('principal://c4-sketch/c4s-123-abc');
|
|
11
|
+
expect(formatTopicReferenceLink({ kind: 'drawing', drawingId: '0b6f7c1e-2a4d-4e8f-9c3b-1d2e3f4a5b6c' }))
|
|
12
|
+
.toBe('principal://drawing/0b6f7c1e-2a4d-4e8f-9c3b-1d2e3f4a5b6c');
|
|
11
13
|
});
|
|
12
14
|
|
|
13
15
|
test('round-trips opaque ids and escapes Markdown delimiters', () => {
|
|
14
16
|
const references: TopicReference[] = [
|
|
15
17
|
{ kind: 'topic', topicId: 'topic-123-abc' },
|
|
16
18
|
{ kind: 'c4-sketch', sketchId: 'c4s-123-abc' },
|
|
19
|
+
{ kind: 'drawing', drawingId: 'drawing-123' },
|
|
17
20
|
{ kind: 'topic', topicId: "topic-é(1)!'?#%" },
|
|
18
21
|
{ kind: 'topic', topicId: 'topic-%2F' },
|
|
19
22
|
];
|
|
@@ -28,6 +31,7 @@ describe('topic reference links', () => {
|
|
|
28
31
|
for (const topicId of ['', '.', '..', 'a/b', 'a\\b', ' a', 'a b', 'a\n', 'a\u0000']) {
|
|
29
32
|
expect(() => formatTopicReferenceLink({ kind: 'topic', topicId })).toThrow();
|
|
30
33
|
}
|
|
34
|
+
expect(() => formatTopicReferenceLink({ kind: 'drawing', drawingId: '../d1' })).toThrow();
|
|
31
35
|
});
|
|
32
36
|
|
|
33
37
|
test('returns null for unsupported or malformed hrefs', () => {
|
|
@@ -40,6 +44,8 @@ describe('topic reference links', () => {
|
|
|
40
44
|
'principal://topic/%20', 'principal://topic/a\n', 'principal://topic/%',
|
|
41
45
|
'principal://topic/%GG', 'principal://topic/%FF',
|
|
42
46
|
' principal://topic/a', 'principal://topic@host/a',
|
|
47
|
+
'principal://drawing/', 'principal://drawing/a/b', 'principal://drawing/..',
|
|
48
|
+
'principal://drawings/a',
|
|
43
49
|
]) {
|
|
44
50
|
expect(parseTopicReferenceLink(href)).toBeNull();
|
|
45
51
|
}
|
|
@@ -6,9 +6,20 @@ function isReferenceId(id: string): boolean {
|
|
|
6
6
|
return id.length > 0 && id !== '.' && id !== '..' && !/[/\\\s\u0000-\u001f\u007f]/.test(id);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
+
function referenceId(reference: TopicReference): string {
|
|
10
|
+
switch (reference.kind) {
|
|
11
|
+
case 'topic':
|
|
12
|
+
return reference.topicId;
|
|
13
|
+
case 'c4-sketch':
|
|
14
|
+
return reference.sketchId;
|
|
15
|
+
case 'drawing':
|
|
16
|
+
return reference.drawingId;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
9
20
|
/** Format a Markdown-safe href. Does not attach or resolve the target. */
|
|
10
21
|
export function formatTopicReferenceLink(reference: TopicReference): string {
|
|
11
|
-
const id = reference
|
|
22
|
+
const id = referenceId(reference);
|
|
12
23
|
if (!isReferenceId(id)) throw new Error('Invalid topic reference id');
|
|
13
24
|
const encoded = encodeURIComponent(id).replace(
|
|
14
25
|
/[!'()*]/g,
|
|
@@ -18,12 +29,12 @@ export function formatTopicReferenceLink(reference: TopicReference): string {
|
|
|
18
29
|
}
|
|
19
30
|
|
|
20
31
|
/**
|
|
21
|
-
* Parse a local topic/sketch href without I/O. Unknown kinds, malformed
|
|
22
|
-
* extra path segments, queries, and fragments return null. Target
|
|
23
|
-
* navigation belong to the host. Parsing never creates an attachment.
|
|
32
|
+
* Parse a local topic/sketch/drawing href without I/O. Unknown kinds, malformed
|
|
33
|
+
* escapes, extra path segments, queries, and fragments return null. Target
|
|
34
|
+
* existence and navigation belong to the host. Parsing never creates an attachment.
|
|
24
35
|
*/
|
|
25
36
|
export function parseTopicReferenceLink(href: string): TopicReference | null {
|
|
26
|
-
const match = /^principal:\/\/(topic|c4-sketch)\/([^/?#]+)$/.exec(href);
|
|
37
|
+
const match = /^principal:\/\/(topic|c4-sketch|drawing)\/([^/?#]+)$/.exec(href);
|
|
27
38
|
if (!match) return null;
|
|
28
39
|
let id: string;
|
|
29
40
|
try {
|
|
@@ -32,7 +43,12 @@ export function parseTopicReferenceLink(href: string): TopicReference | null {
|
|
|
32
43
|
return null;
|
|
33
44
|
}
|
|
34
45
|
if (!isReferenceId(id)) return null;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
switch (match[1]) {
|
|
47
|
+
case 'topic':
|
|
48
|
+
return { kind: 'topic', topicId: id };
|
|
49
|
+
case 'c4-sketch':
|
|
50
|
+
return { kind: 'c4-sketch', sketchId: id };
|
|
51
|
+
default:
|
|
52
|
+
return { kind: 'drawing', drawingId: id };
|
|
53
|
+
}
|
|
38
54
|
}
|
|
@@ -60,9 +60,11 @@ export interface TopicSubsystemModelRef {
|
|
|
60
60
|
* A directional, local-only reference owned by a topic. Targets remain separate
|
|
61
61
|
* artifacts; their contents and titles are resolved by the host, not copied here.
|
|
62
62
|
* Referencing a C4 sketch does not make its authored hypothesis evidence.
|
|
63
|
+
* A drawing ID is the `.excalidraw` file stem under the host's drawings folder.
|
|
63
64
|
*/
|
|
64
65
|
export type TopicReference =
|
|
65
66
|
| { kind: 'c4-sketch'; sketchId: string }
|
|
67
|
+
| { kind: 'drawing'; drawingId: string }
|
|
66
68
|
| { kind: 'topic'; topicId: string };
|
|
67
69
|
|
|
68
70
|
/**
|
|
@@ -102,7 +104,7 @@ export interface DraftTopic {
|
|
|
102
104
|
*/
|
|
103
105
|
subsystemModels?: TopicSubsystemModelRef[];
|
|
104
106
|
/**
|
|
105
|
-
* Attached topics
|
|
107
|
+
* Attached topics, C4 sketches, and drawings. Whole-array replacement on update; `[]`
|
|
106
108
|
* clears attachments. Independent of links in `description`: neither edits
|
|
107
109
|
* the other. Hosts validate target existence and reject self-references.
|
|
108
110
|
*/
|
|
@@ -96,6 +96,7 @@ describe('CRUD', () => {
|
|
|
96
96
|
const store = makeStore();
|
|
97
97
|
const references: TopicReference[] = [
|
|
98
98
|
{ kind: 'c4-sketch', sketchId: 'c4s-1-abc' },
|
|
99
|
+
{ kind: 'drawing', drawingId: 'drawing-1' },
|
|
99
100
|
{ kind: 'topic', topicId: 'topic-related' },
|
|
100
101
|
];
|
|
101
102
|
const created = await store.createTopic({
|