@principal-ai/principal-view-react 0.16.44 → 0.16.46

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 (31) hide show
  1. package/dist/components/session-events/SessionEventFeed.d.ts +5 -4
  2. package/dist/components/session-events/SessionEventFeed.d.ts.map +1 -1
  3. package/dist/components/session-events/SessionEventFeed.js +102 -5
  4. package/dist/components/session-events/SessionEventFeed.js.map +1 -1
  5. package/dist/subsystem/ComponentDeclaration.d.ts +3 -1
  6. package/dist/subsystem/ComponentDeclaration.d.ts.map +1 -1
  7. package/dist/subsystem/ComponentDeclaration.js +49 -75
  8. package/dist/subsystem/ComponentDeclaration.js.map +1 -1
  9. package/dist/subsystem/formatDeclaration.d.ts +11 -0
  10. package/dist/subsystem/formatDeclaration.d.ts.map +1 -0
  11. package/dist/subsystem/formatDeclaration.js +96 -0
  12. package/dist/subsystem/formatDeclaration.js.map +1 -0
  13. package/dist/subsystem/model.d.ts +11 -0
  14. package/dist/subsystem/model.d.ts.map +1 -1
  15. package/dist/subsystem/model.js.map +1 -1
  16. package/dist/subsystem/tokenizeComponent.d.ts +19 -0
  17. package/dist/subsystem/tokenizeComponent.d.ts.map +1 -0
  18. package/dist/subsystem/tokenizeComponent.js +61 -0
  19. package/dist/subsystem/tokenizeComponent.js.map +1 -0
  20. package/dist/subsystem/tokenizeFormatted.d.ts +15 -0
  21. package/dist/subsystem/tokenizeFormatted.d.ts.map +1 -0
  22. package/dist/subsystem/tokenizeFormatted.js +84 -0
  23. package/dist/subsystem/tokenizeFormatted.js.map +1 -0
  24. package/package.json +3 -2
  25. package/src/components/session-events/SessionEventFeed.tsx +97 -6
  26. package/src/stories/ComponentDeclarationAudit.stories.tsx +290 -0
  27. package/src/subsystem/ComponentDeclaration.tsx +61 -269
  28. package/src/subsystem/formatDeclaration.ts +116 -0
  29. package/src/subsystem/model.ts +24 -0
  30. package/src/subsystem/tokenizeComponent.ts +70 -0
  31. package/src/subsystem/tokenizeFormatted.ts +92 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Generate a TypeScript declaration string from a SubsystemComponent's detail.
3
+ *
4
+ * This is the "source code" that Prettier will format. The output is valid
5
+ * TypeScript (except for `external`, which is handled separately). The string
6
+ * is intentionally simple — no indentation, no line breaks — because Prettier
7
+ * handles all formatting.
8
+ */
9
+
10
+ import type { SubsystemComponent } from './model';
11
+ import type { GraphifyComponentDetail } from '../graphify';
12
+
13
+ export function generateDeclarationString(component: SubsystemComponent): string {
14
+ const detail = component.detail;
15
+ const kind = detail?.kind ?? component.kind;
16
+ const name = component.symbol || component.name || 'untitled';
17
+
18
+ switch (kind) {
19
+ case 'class':
20
+ return generateClass(name, detail);
21
+ case 'function':
22
+ return generateFunction(name, detail);
23
+ case 'type':
24
+ return generateType(name, detail);
25
+ case 'module':
26
+ return generateModule(detail);
27
+ case 'external':
28
+ // Not valid TypeScript — caller should handle formatting.
29
+ return `external '${detail?.kind === 'external' ? detail.label : name}'`;
30
+ default:
31
+ return `${kind} ${name}`;
32
+ }
33
+ }
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Helpers
37
+ // ---------------------------------------------------------------------------
38
+
39
+ /** Format parameters, synthesising names for unnamed positionals. */
40
+ function formatParams(params: { name?: string; type: string }[]): string {
41
+ return params
42
+ .map((p, i) => (p.name ? `${p.name}: ${p.type}` : `arg${i}: ${p.type}`))
43
+ .join(', ');
44
+ }
45
+
46
+ // ---------------------------------------------------------------------------
47
+ // Per-kind generators
48
+ // ---------------------------------------------------------------------------
49
+
50
+ function generateClass(name: string, detail?: GraphifyComponentDetail): string {
51
+ const cls = detail?.kind === 'class' ? detail : undefined;
52
+ const parts: string[] = [`class ${name}`];
53
+
54
+ if (cls?.extends && cls.extends.length > 0) {
55
+ parts.push(`extends ${cls.extends.join(', ')}`);
56
+ }
57
+ if (cls?.implements && cls.implements.length > 0) {
58
+ parts.push(`implements ${cls.implements.join(', ')}`);
59
+ }
60
+
61
+ const members: string[] = [];
62
+
63
+ for (const m of cls?.methods ?? []) {
64
+ const ret = m.returnType ? `: ${m.returnType}` : '';
65
+ members.push(` ${m.name}(${formatParams(m.parameters ?? [])})${ret};`);
66
+ }
67
+
68
+ for (const prop of cls?.properties ?? []) {
69
+ const t = prop.type ? `: ${prop.type}` : '';
70
+ members.push(` ${prop.name}${t};`);
71
+ }
72
+
73
+ if (members.length > 0) {
74
+ parts.push(`{\n${members.join('\n')}\n}`);
75
+ } else {
76
+ parts.push('{}');
77
+ }
78
+
79
+ return parts.join(' ');
80
+ }
81
+
82
+ function generateFunction(name: string, detail?: GraphifyComponentDetail): string {
83
+ const fn = detail?.kind === 'function' ? detail : undefined;
84
+ const params = formatParams(fn?.parameters ?? []);
85
+ const ret = fn?.returnType ? `: ${fn.returnType}` : '';
86
+ return `function ${name}(${params})${ret};`;
87
+ }
88
+
89
+ function generateType(name: string, detail?: GraphifyComponentDetail): string {
90
+ const tpe = detail?.kind === 'type' ? detail : undefined;
91
+ const props = (tpe?.properties ?? [])
92
+ .map((p) => ` ${p.name}${p.type ? `: ${p.type}` : ''};`)
93
+ .join('\n');
94
+
95
+ if (props) {
96
+ return `interface ${name} {\n${props}\n}`;
97
+ }
98
+ return `interface ${name} {}`;
99
+ }
100
+
101
+ function generateModule(detail?: GraphifyComponentDetail): string {
102
+ const mod = detail?.kind === 'module' ? detail : undefined;
103
+ if (!mod) return 'module {}';
104
+
105
+ const parts: string[] = [];
106
+
107
+ for (const imp of mod.imports ?? []) {
108
+ parts.push(`import '${imp.name}';`);
109
+ }
110
+
111
+ if ((mod.exports ?? []).length > 0) {
112
+ parts.push(`export { ${mod.exports!.join(', ')} };`);
113
+ }
114
+
115
+ return parts.join('\n') || `module {}`;
116
+ }
@@ -27,6 +27,24 @@ export type SubsystemComponentKind =
27
27
  | 'module'
28
28
  | 'external';
29
29
 
30
+ // ---------------------------------------------------------------------------
31
+ // Declaration tokens — structured source representation
32
+ // ---------------------------------------------------------------------------
33
+
34
+ export type SubsystemDeclTokenKind =
35
+ | 'keyword'
36
+ | 'name'
37
+ | 'member'
38
+ | 'type'
39
+ | 'punctuation'
40
+ | 'string'
41
+ | 'newline';
42
+
43
+ export interface SubsystemDeclToken {
44
+ text: string;
45
+ kind: SubsystemDeclTokenKind;
46
+ }
47
+
30
48
  export type SubsystemEdgeMechanism =
31
49
  | 'imports'
32
50
  | 'imports_from'
@@ -87,6 +105,12 @@ export interface SubsystemComponent {
87
105
  * may claim `verified`.
88
106
  */
89
107
  detailProvenance?: 'verified' | 'authored';
108
+ /**
109
+ * Pre-tokenized declaration for the detail panel. When present, the
110
+ * renderer skips client-side tokenization. Tokens are language-agnostic;
111
+ * a different language just needs a different tokenizer and text joiner.
112
+ */
113
+ tokens?: SubsystemDeclToken[];
90
114
  }
91
115
 
92
116
  /** A cross-component edge in the subsystem graph. */
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Tokenize a SubsystemComponent into a flat token stream for the detail panel.
3
+ *
4
+ * Pipeline: generate declaration string → format with Prettier → tokenize
5
+ * with highlight.js. The `component.tokens` field, when present, overrides
6
+ * the entire pipeline (for pre-tokenized data from graphify).
7
+ *
8
+ * This function is async because Prettier's format() is async.
9
+ */
10
+
11
+ import type { SubsystemComponent, SubsystemDeclToken } from './model';
12
+ import { generateDeclarationString } from './formatDeclaration';
13
+ import { tokenizeFormatted } from './tokenizeFormatted';
14
+
15
+ // Lazy-loaded Prettier to avoid startup cost.
16
+ let prettierPromise: Promise<typeof import('prettier/standalone')> | null = null;
17
+ let prettierPluginsPromise: Promise<{
18
+ typescript: typeof import('prettier/plugins/typescript');
19
+ estree: typeof import('prettier/plugins/estree');
20
+ }> | null = null;
21
+
22
+ async function getPrettier() {
23
+ if (!prettierPromise) {
24
+ prettierPromise = import('prettier/standalone');
25
+ }
26
+ if (!prettierPluginsPromise) {
27
+ prettierPluginsPromise = Promise.all([
28
+ import('prettier/plugins/typescript'),
29
+ import('prettier/plugins/estree'),
30
+ ]).then(([typescript, estree]) => ({ typescript, estree }));
31
+ }
32
+ const [prettier, plugins] = await Promise.all([prettierPromise, prettierPluginsPromise]);
33
+ return { prettier, plugins };
34
+ }
35
+
36
+ /**
37
+ * Tokenize a SubsystemComponent into SubsystemDeclToken[].
38
+ *
39
+ * When `component.tokens` is present (pre-tokenized data from the wire),
40
+ * it's returned as-is. Otherwise the pipeline generates a TypeScript
41
+ * declaration string, formats it with Prettier, and tokenizes the output.
42
+ */
43
+ export async function tokenizeComponent(component: SubsystemComponent): Promise<SubsystemDeclToken[]> {
44
+ // Pre-tokenized tokens from the wire take precedence.
45
+ if (component.tokens) return component.tokens;
46
+
47
+ // External kind — not valid TypeScript, bypass Prettier.
48
+ const kind = component.detail?.kind ?? component.kind;
49
+ if (kind === 'external') {
50
+ const label = component.detail?.kind === 'external' ? component.detail.label : component.name;
51
+ return [
52
+ { text: 'external', kind: 'keyword' },
53
+ { text: ' ', kind: 'punctuation' },
54
+ { text: "'", kind: 'punctuation' },
55
+ { text: label, kind: 'string' },
56
+ { text: "'", kind: 'punctuation' },
57
+ ];
58
+ }
59
+
60
+ // Generate → format → tokenize
61
+ const raw = generateDeclarationString(component);
62
+ const { prettier, plugins } = await getPrettier();
63
+ const formatted = await prettier.format(raw, {
64
+ parser: 'typescript',
65
+ plugins: [plugins.typescript, plugins.estree],
66
+ printWidth: 80,
67
+ });
68
+
69
+ return tokenizeFormatted(formatted);
70
+ }
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Tokenize a formatted TypeScript declaration string into SubsystemDeclToken[]
3
+ * using highlight.js.
4
+ *
5
+ * highlight.js produces HTML with `<span class="hljs-...">` wrappers. This
6
+ * module parses that output into our token format so the renderer stays
7
+ * language-agnostic.
8
+ */
9
+
10
+ import hljs from 'highlight.js/lib/core';
11
+ import typescript from 'highlight.js/lib/languages/typescript';
12
+ import type { SubsystemDeclToken, SubsystemDeclTokenKind } from './model';
13
+
14
+ // Register once — safe to call multiple times.
15
+ try {
16
+ hljs.registerLanguage('typescript', typescript);
17
+ } catch {
18
+ // Already registered.
19
+ }
20
+
21
+ // hljs class → our token kind
22
+ const CLASS_MAP: Record<string, SubsystemDeclTokenKind> = {
23
+ 'hljs-keyword': 'keyword',
24
+ 'hljs-title': 'name',
25
+ 'hljs-title.function_': 'name',
26
+ 'hljs-params': 'member',
27
+ 'hljs-type': 'type',
28
+ 'hljs-built_in': 'type',
29
+ 'hljs-string': 'string',
30
+ 'hljs-number': 'string',
31
+ 'hljs-comment': 'punctuation',
32
+ };
33
+
34
+ /**
35
+ * Tokenize a formatted TypeScript string into SubsystemDeclToken[].
36
+ * The input should already be formatted by Prettier (or be valid TS).
37
+ */
38
+ export function tokenizeFormatted(code: string): SubsystemDeclToken[] {
39
+ const result = hljs.highlight(code, { language: 'typescript' });
40
+ return parseHighlightedHtml(result.value);
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // HTML parser — walks the hljs output and extracts tokens
45
+ // ---------------------------------------------------------------------------
46
+
47
+ function unescapeHtml(text: string): string {
48
+ return text
49
+ .replace(/&quot;/g, '"')
50
+ .replace(/&lt;/g, '<')
51
+ .replace(/&gt;/g, '>')
52
+ .replace(/&amp;/g, '&')
53
+ .replace(/&#39;/g, "'");
54
+ }
55
+
56
+ function parseHighlightedHtml(html: string): SubsystemDeclToken[] {
57
+ const tokens: SubsystemDeclToken[] = [];
58
+ // Regex matches either `<span class="hljs-...">`, `</span>`, or text content.
59
+ const re = /<span class="([^"]+)">|<\/span>|[^<]+/g;
60
+ const classStack: string[] = [];
61
+ let match: RegExpExecArray | null;
62
+
63
+ while ((match = re.exec(html)) !== null) {
64
+ const chunk = match[0];
65
+
66
+ if (chunk.startsWith('<span')) {
67
+ // Opening tag — push class onto stack.
68
+ classStack.push(match[1]);
69
+ } else if (chunk === '</span>') {
70
+ // Closing tag — pop class.
71
+ classStack.pop();
72
+ } else {
73
+ // Text content — emit tokens for each line segment.
74
+ const currentClass = classStack[classStack.length - 1] ?? '';
75
+ const kind = CLASS_MAP[currentClass] ?? 'punctuation';
76
+ const text = chunk;
77
+
78
+ // Split on newlines so each line becomes its own token set.
79
+ const lines = text.split('\n');
80
+ for (let i = 0; i < lines.length; i++) {
81
+ if (i > 0) {
82
+ tokens.push({ text: '', kind: 'newline' });
83
+ }
84
+ if (lines[i]) {
85
+ tokens.push({ text: unescapeHtml(lines[i]), kind });
86
+ }
87
+ }
88
+ }
89
+ }
90
+
91
+ return tokens;
92
+ }