@principal-ai/subsystems-react 0.24.2 → 0.24.4
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/dist/components/session-events/SessionEventFeed.d.ts +2 -1
- package/dist/components/session-events/SessionEventFeed.d.ts.map +1 -1
- package/dist/components/session-events/SessionEventFeed.js +43 -35
- package/dist/components/session-events/SessionEventFeed.js.map +1 -1
- package/dist/pierre/PierreFileView.d.ts.map +1 -1
- package/dist/pierre/PierreFileView.js +23 -1
- package/dist/pierre/PierreFileView.js.map +1 -1
- package/dist/pierre/PierreSnippetView.d.ts.map +1 -1
- package/dist/pierre/PierreSnippetView.js +21 -1
- package/dist/pierre/PierreSnippetView.js.map +1 -1
- package/dist/subsystem/IssueList.d.ts.map +1 -1
- package/dist/subsystem/IssueList.js +10 -12
- package/dist/subsystem/IssueList.js.map +1 -1
- package/package.json +1 -1
- package/src/components/session-events/SessionEventFeed.tsx +54 -45
- package/src/pierre/PierreFileView.tsx +27 -1
- package/src/pierre/PierreSnippetView.tsx +25 -1
- package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +66 -4
- package/src/subsystem/IssueList.tsx +10 -12
|
@@ -7,6 +7,11 @@
|
|
|
7
7
|
|
|
8
8
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
9
9
|
import { File } from '@pierre/diffs/react';
|
|
10
|
+
import {
|
|
11
|
+
getFiletypeFromFileName,
|
|
12
|
+
getHighlighterOptions,
|
|
13
|
+
preloadHighlighter,
|
|
14
|
+
} from '@pierre/diffs';
|
|
10
15
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
11
16
|
import { buildPierreOptions, PIERRE_FILE_STYLE } from './pierreBackground';
|
|
12
17
|
import { pierreLangForPath } from './pierreFileLang';
|
|
@@ -33,6 +38,12 @@ export function PierreFileView({
|
|
|
33
38
|
const { theme } = useTheme();
|
|
34
39
|
const [contents, setContents] = useState<string | null>(null);
|
|
35
40
|
const [error, setError] = useState<string | null>(null);
|
|
41
|
+
// Pierre highlights on the main thread with no worker pool: a cold first
|
|
42
|
+
// render paints an empty <pre> and never re-renders once Shiki resolves.
|
|
43
|
+
// Warm the shared highlighter for this file before mounting <File> so the
|
|
44
|
+
// very first render produces content.
|
|
45
|
+
const [highlighterReady, setHighlighterReady] = useState(false);
|
|
46
|
+
const lang = pierreLangForPath(filePath) ?? getFiletypeFromFileName(fileName);
|
|
36
47
|
|
|
37
48
|
const fileObject = useMemo(
|
|
38
49
|
() =>
|
|
@@ -60,6 +71,21 @@ export function PierreFileView({
|
|
|
60
71
|
};
|
|
61
72
|
}, [filePath, readFile]);
|
|
62
73
|
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
let cancelled = false;
|
|
76
|
+
setHighlighterReady(false);
|
|
77
|
+
void preloadHighlighter(getHighlighterOptions(lang, {}))
|
|
78
|
+
.catch(() => {
|
|
79
|
+
// Fall through: let <File> attempt its own (plain-text) render.
|
|
80
|
+
})
|
|
81
|
+
.then(() => {
|
|
82
|
+
if (!cancelled) setHighlighterReady(true);
|
|
83
|
+
});
|
|
84
|
+
return () => {
|
|
85
|
+
cancelled = true;
|
|
86
|
+
};
|
|
87
|
+
}, [lang]);
|
|
88
|
+
|
|
63
89
|
const onPostRender = useCallback(
|
|
64
90
|
(fileContainer: HTMLElement) => {
|
|
65
91
|
if (focusLine == null) return;
|
|
@@ -83,7 +109,7 @@ export function PierreFileView({
|
|
|
83
109
|
</div>
|
|
84
110
|
);
|
|
85
111
|
}
|
|
86
|
-
if (fileObject === null) {
|
|
112
|
+
if (fileObject === null || !highlighterReady) {
|
|
87
113
|
return (
|
|
88
114
|
<div style={{ padding: 16, color: theme.colors.textSecondary }}>
|
|
89
115
|
Loading…
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
|
|
11
11
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
12
12
|
import { File } from '@pierre/diffs/react';
|
|
13
|
+
import {
|
|
14
|
+
getFiletypeFromFileName,
|
|
15
|
+
getHighlighterOptions,
|
|
16
|
+
preloadHighlighter,
|
|
17
|
+
} from '@pierre/diffs';
|
|
13
18
|
import { useTheme } from '@principal-ade/industry-theme';
|
|
14
19
|
import { buildPierreOptions, PIERRE_FILE_STYLE } from './pierreBackground';
|
|
15
20
|
import { pierreLangForPath } from './pierreFileLang';
|
|
@@ -47,6 +52,25 @@ export function PierreSnippetView({
|
|
|
47
52
|
const { theme } = useTheme();
|
|
48
53
|
const [contents, setContents] = useState<string | null>(null);
|
|
49
54
|
const [error, setError] = useState<string | null>(null);
|
|
55
|
+
// Warm the shared highlighter before mounting <File>; a cold first render
|
|
56
|
+
// paints an empty <pre> that never re-renders (no worker pool).
|
|
57
|
+
const [highlighterReady, setHighlighterReady] = useState(false);
|
|
58
|
+
const lang = pierreLangForPath(filePath) ?? getFiletypeFromFileName(fileName);
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
let cancelled = false;
|
|
62
|
+
setHighlighterReady(false);
|
|
63
|
+
void preloadHighlighter(getHighlighterOptions(lang, {}))
|
|
64
|
+
.catch(() => {
|
|
65
|
+
// Fall through: let <File> attempt its own (plain-text) render.
|
|
66
|
+
})
|
|
67
|
+
.then(() => {
|
|
68
|
+
if (!cancelled) setHighlighterReady(true);
|
|
69
|
+
});
|
|
70
|
+
return () => {
|
|
71
|
+
cancelled = true;
|
|
72
|
+
};
|
|
73
|
+
}, [lang]);
|
|
50
74
|
|
|
51
75
|
useEffect(() => {
|
|
52
76
|
let cancelled = false;
|
|
@@ -116,7 +140,7 @@ export function PierreSnippetView({
|
|
|
116
140
|
</div>
|
|
117
141
|
);
|
|
118
142
|
}
|
|
119
|
-
if (!fileObject || !slice) {
|
|
143
|
+
if (!fileObject || !slice || !highlighterReady) {
|
|
120
144
|
return (
|
|
121
145
|
<div style={{ padding: 16, color: theme.colors.textSecondary }}>
|
|
122
146
|
Loading…
|
|
@@ -70,10 +70,68 @@ const ISSUES: SubsystemIssue[] = [
|
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
id: 'i7',
|
|
73
|
-
severity: '
|
|
73
|
+
severity: 'info',
|
|
74
74
|
kind: 'boundary_process_nest_disagree',
|
|
75
|
-
message:
|
|
76
|
-
|
|
75
|
+
message:
|
|
76
|
+
'module "src/host/main.ts" members claim different processes: "host", "renderer"',
|
|
77
|
+
target: { kind: 'module', id: 'src/host/main.ts', label: 'src/host/main.ts' },
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'i8',
|
|
81
|
+
severity: 'warn',
|
|
82
|
+
kind: 'unverified_symbol',
|
|
83
|
+
message: 'Symbol persistSession unverified — Graphify anchor file-only: persistSession',
|
|
84
|
+
target: { kind: 'component', id: 'session-store', label: 'SessionStore' },
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: 'i9',
|
|
88
|
+
severity: 'error',
|
|
89
|
+
kind: 'stale_declaration',
|
|
90
|
+
message: 'Declaration moved since capture — recorded start line 42, now 57.',
|
|
91
|
+
target: { kind: 'component', id: 'session-store', label: 'SessionStore' },
|
|
92
|
+
fix: { label: 'Re-pin declaration' },
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
id: 'i10',
|
|
96
|
+
severity: 'info',
|
|
97
|
+
kind: 'construct_unconfirmed',
|
|
98
|
+
message: 'Graphify anchored the node but left the construct unclassified (interface vs type alias).',
|
|
99
|
+
target: { kind: 'component', id: 'host-info', label: 'HostInfo' },
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'i11',
|
|
103
|
+
severity: 'error',
|
|
104
|
+
kind: 'signature_mismatch',
|
|
105
|
+
message: 'Claimed (req: HostInfo) => Session; Graphify has (string) => void.',
|
|
106
|
+
target: { kind: 'component', id: 'entry', label: 'checkoutApi' },
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
id: 'i13',
|
|
110
|
+
severity: 'info',
|
|
111
|
+
kind: 'unresolved',
|
|
112
|
+
message: 'Repo root for pkg:github/you/your-app is unresolved — source checks skipped.',
|
|
113
|
+
target: { kind: 'component', id: 'stripe', label: 'Stripe' },
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'i14',
|
|
117
|
+
severity: 'info',
|
|
118
|
+
kind: 'topology_import_unconfirmed',
|
|
119
|
+
message: 'No import edge from checkoutApi to cartStore found in Graphify.',
|
|
120
|
+
target: { kind: 'relation', id: 'r1', label: 'checkoutApi → cartStore', detail: 'imports' },
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
id: 'i15',
|
|
124
|
+
severity: 'info',
|
|
125
|
+
kind: 'topology_relation_unconfirmed',
|
|
126
|
+
message: 'Graphify found no inheritance evidence for FileSessionStore → SessionStore.',
|
|
127
|
+
target: { kind: 'relation', id: 'r2', label: 'FileSessionStore → SessionStore', detail: 'extends' },
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'i16',
|
|
131
|
+
severity: 'error',
|
|
132
|
+
kind: 'boundary_module_without_file',
|
|
133
|
+
message: 'module "src/checkout" set but file is empty — nowhere to ground the frame.',
|
|
134
|
+
target: { kind: 'module', id: 'src/checkout', label: 'src/checkout' },
|
|
77
135
|
},
|
|
78
136
|
];
|
|
79
137
|
|
|
@@ -120,7 +178,11 @@ export const GraphWithIssues: Story = {
|
|
|
120
178
|
walkthroughs={graphEdges.walkthroughs}
|
|
121
179
|
title="Checkout"
|
|
122
180
|
description="A small e-commerce checkout subsystem."
|
|
123
|
-
diagnostic={{
|
|
181
|
+
diagnostic={{
|
|
182
|
+
status: 'issues',
|
|
183
|
+
issueCount: ISSUES.filter((i) => i.severity === 'error' || i.severity === 'warn').length,
|
|
184
|
+
onOpen: () => {},
|
|
185
|
+
}}
|
|
124
186
|
issues={ISSUES}
|
|
125
187
|
onSelectIssue={() => {}}
|
|
126
188
|
onApplyIssueFix={() => {}}
|
|
@@ -60,13 +60,12 @@ export const SUBSYSTEM_ISSUE_CATEGORY_LABEL: Record<
|
|
|
60
60
|
/** Default `kind` → layer. `boundary_*` splits by module (static) vs process (runtime). */
|
|
61
61
|
const KIND_CATEGORY: Record<string, SubsystemIssueCategory> = {
|
|
62
62
|
missing_file: 'construct',
|
|
63
|
-
|
|
63
|
+
unverified_symbol: 'construct',
|
|
64
64
|
stale_declaration: 'construct',
|
|
65
65
|
construct_mismatch: 'construct',
|
|
66
66
|
construct_unconfirmed: 'construct',
|
|
67
67
|
signature_mismatch: 'construct',
|
|
68
68
|
signature_unconfirmed: 'construct',
|
|
69
|
-
anchor: 'construct',
|
|
70
69
|
unresolved: 'construct',
|
|
71
70
|
topology_broken_endpoint: 'static-topology',
|
|
72
71
|
topology_import_unconfirmed: 'static-topology',
|
|
@@ -126,25 +125,24 @@ export function humanizeIssueKind(kind: string): string {
|
|
|
126
125
|
/**
|
|
127
126
|
* Curated short labels, trimmed of prefixes the grouping already implies
|
|
128
127
|
* (`topology_`, `boundary_`) but kept unambiguous within a layer — so
|
|
129
|
-
* `construct_mismatch` reads "
|
|
130
|
-
* would collide with `signature_mismatch`. Explicit `issue.kindLabel` wins.
|
|
128
|
+
* `construct_mismatch` reads "Construct mismatch" rather than a bare "Mismatch"
|
|
129
|
+
* that would collide with `signature_mismatch`. Explicit `issue.kindLabel` wins.
|
|
131
130
|
*/
|
|
132
131
|
const KIND_LABEL: Record<string, string> = {
|
|
133
|
-
missing_file: '
|
|
134
|
-
|
|
132
|
+
missing_file: 'Declaration file missing',
|
|
133
|
+
unverified_symbol: 'Unverified symbol',
|
|
135
134
|
stale_declaration: 'Stale declaration',
|
|
136
|
-
construct_mismatch: '
|
|
135
|
+
construct_mismatch: 'Construct mismatch',
|
|
137
136
|
construct_unconfirmed: 'Unconfirmed construct',
|
|
138
137
|
signature_mismatch: 'Signature mismatch',
|
|
139
138
|
signature_unconfirmed: 'Signature unconfirmed',
|
|
140
|
-
anchor: 'Anchor',
|
|
141
139
|
unresolved: 'Unresolved',
|
|
142
|
-
topology_broken_endpoint: '
|
|
140
|
+
topology_broken_endpoint: 'Construct relation broken',
|
|
143
141
|
topology_import_unconfirmed: 'Import unconfirmed',
|
|
144
142
|
topology_relation_unconfirmed: 'Relation unconfirmed',
|
|
145
|
-
boundary_module_without_file: '
|
|
146
|
-
boundary_module_file_mismatch: '
|
|
147
|
-
boundary_process_nest_disagree: '
|
|
143
|
+
boundary_module_without_file: 'Declaration file undefined',
|
|
144
|
+
boundary_module_file_mismatch: 'Declaration file outside module',
|
|
145
|
+
boundary_process_nest_disagree: 'Module spans multiple process contexts',
|
|
148
146
|
walkthrough: 'Step issue',
|
|
149
147
|
};
|
|
150
148
|
|