@opetope/devtools 0.1.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 +5 -0
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/README.ru.md +105 -0
- package/dist/activity-export.d.ts +4 -0
- package/dist/activity.d.ts +63 -0
- package/dist/conformance-activity.d.ts +8 -0
- package/dist/conformance.d.ts +37 -0
- package/dist/control-subject.d.ts +15 -0
- package/dist/control-ui-DMmVy9Ob.js +16 -0
- package/dist/control-ui-DMmVy9Ob.js.map +1 -0
- package/dist/control.d.ts +45 -0
- package/dist/corpus.d.ts +33 -0
- package/dist/decode-activity.d.ts +4 -0
- package/dist/decode-shapes.d.ts +32 -0
- package/dist/decode-values.d.ts +36 -0
- package/dist/decode.d.ts +41 -0
- package/dist/digest.d.ts +17 -0
- package/dist/export-run-DIQZax_d.js +2 -0
- package/dist/export-run-DIQZax_d.js.map +1 -0
- package/dist/export-snapshot-Buw-c6ZG.js +2 -0
- package/dist/export-snapshot-Buw-c6ZG.js.map +1 -0
- package/dist/export-snapshot.d.ts +34 -0
- package/dist/extension-points.d.ts +45 -0
- package/dist/fixture-plan.d.ts +62 -0
- package/dist/fixture-runtime.d.ts +37 -0
- package/dist/fixture.d.ts +23 -0
- package/dist/fold-snapshot-DjiURn8v.js +2 -0
- package/dist/fold-snapshot-DjiURn8v.js.map +1 -0
- package/dist/fold-snapshot.d.ts +15 -0
- package/dist/graph-index.d.ts +104 -0
- package/dist/graph-store.d.ts +77 -0
- package/dist/graph.d.ts +211 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/inspector.d.ts +70 -0
- package/dist/install.d.ts +44 -0
- package/dist/install.js +2 -0
- package/dist/install.js.map +1 -0
- package/dist/integration.d.ts +42 -0
- package/dist/integration.js +2 -0
- package/dist/integration.js.map +1 -0
- package/dist/legend-DT7j0_pD.js +2 -0
- package/dist/legend-DT7j0_pD.js.map +1 -0
- package/dist/node-facts.d.ts +20 -0
- package/dist/node-state.d.ts +152 -0
- package/dist/optional.d.ts +9 -0
- package/dist/panel-5jvisQBr.js +367 -0
- package/dist/panel-5jvisQBr.js.map +1 -0
- package/dist/producer-ids.d.ts +15 -0
- package/dist/protocol-joKTjlMj.js +2 -0
- package/dist/protocol-joKTjlMj.js.map +1 -0
- package/dist/protocol.d.ts +238 -0
- package/dist/react/activity-rows.d.ts +12 -0
- package/dist/react/activity-view.d.ts +9 -0
- package/dist/react/control-locale.d.ts +25 -0
- package/dist/react/control-styles.d.ts +7 -0
- package/dist/react/control-ui.d.ts +37 -0
- package/dist/react/edge-filter.d.ts +36 -0
- package/dist/react/edge-filters.d.ts +17 -0
- package/dist/react/export-delivery.d.ts +33 -0
- package/dist/react/export-run.d.ts +20 -0
- package/dist/react/format.d.ts +17 -0
- package/dist/react/glyph.d.ts +20 -0
- package/dist/react/graph-layout.d.ts +67 -0
- package/dist/react/graph-view.d.ts +30 -0
- package/dist/react/graph-viewport.d.ts +48 -0
- package/dist/react/icons.d.ts +12 -0
- package/dist/react/inspector-view.d.ts +29 -0
- package/dist/react/label.d.ts +28 -0
- package/dist/react/lazy-module.d.ts +38 -0
- package/dist/react/legend-locale.d.ts +23 -0
- package/dist/react/legend.d.ts +10 -0
- package/dist/react/locale-context.d.ts +17 -0
- package/dist/react/locale.d.ts +115 -0
- package/dist/react/main-column.d.ts +22 -0
- package/dist/react/panel-header.d.ts +20 -0
- package/dist/react/panel-mode.d.ts +3 -0
- package/dist/react/panel-status.d.ts +19 -0
- package/dist/react/panel.d.ts +41 -0
- package/dist/react/search-results.d.ts +18 -0
- package/dist/react/selection.d.ts +15 -0
- package/dist/react/styles.d.ts +13 -0
- package/dist/react/theme.d.ts +39 -0
- package/dist/react/timeline-locale.d.ts +20 -0
- package/dist/react/timeline-view.d.ts +14 -0
- package/dist/react/tree.d.ts +26 -0
- package/dist/react/use-store.d.ts +11 -0
- package/dist/react.d.ts +16 -0
- package/dist/react.js +2 -0
- package/dist/react.js.map +1 -0
- package/dist/search-CC7avy6I.js +2 -0
- package/dist/search-CC7avy6I.js.map +1 -0
- package/dist/search.d.ts +20 -0
- package/dist/session-qjKT0E69.js +2 -0
- package/dist/session-qjKT0E69.js.map +1 -0
- package/dist/session.d.ts +59 -0
- package/dist/testing.d.ts +10 -0
- package/dist/testing.js +4 -0
- package/dist/testing.js.map +1 -0
- package/dist/timeline-view-ByPB1a7-.js +2 -0
- package/dist/timeline-view-ByPB1a7-.js.map +1 -0
- package/dist/timeline.d.ts +57 -0
- package/dist/visual-axes.d.ts +35 -0
- package/package.json +107 -0
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Executable form of the diagnostic protocol. `docs/devtools.md` §2 sketched it before the
|
|
3
|
+
* decisions D68–D112 of [`decisions.md`](../../docs/decisions.md); where the two disagree the runtime wins, and it is
|
|
4
|
+
* recorded in the decision log of document 14 rather than by editing its prose.
|
|
5
|
+
*
|
|
6
|
+
* The protocol is data-only by construction: no callbacks, no `FeatureInstance`, no models, resources or repository
|
|
7
|
+
* objects, no raw `Error`, `AbortSignal` or thenable, no DOM or React instance (§2.6). Failures carry the subject and
|
|
8
|
+
* the code of the runtime's own error classes and nothing else — never a message, a stack or a cause (§2.6).
|
|
9
|
+
*
|
|
10
|
+
* The id brands are *optional* markers, and that is the whole of decision T29. `RuntimeInspectionSession` is a
|
|
11
|
+
* consumed interface: the runtime produces these ids and declares them `string` and `number`, so a required brand
|
|
12
|
+
* would have made the producer unable to satisfy the very interface written for it, and every host attaching the
|
|
13
|
+
* panel would have carried a cast. An optional marker keeps the names — a signature still reads `DiagnosticNodeId`,
|
|
14
|
+
* not `string` — while a plain string the runtime built is accepted as it is. What it does not do is keep the kinds
|
|
15
|
+
* apart at compile time; the guarantee that an id is well formed lives where it can be enforced, in the validators
|
|
16
|
+
* below and in the hostile decoder (§7).
|
|
17
|
+
*/
|
|
18
|
+
declare const conditionIdBrand: unique symbol;
|
|
19
|
+
declare const edgeIdBrand: unique symbol;
|
|
20
|
+
declare const nodeIdBrand: unique symbol;
|
|
21
|
+
declare const planFingerprintBrand: unique symbol;
|
|
22
|
+
declare const sequenceBrand: unique symbol;
|
|
23
|
+
declare const sessionIdBrand: unique symbol;
|
|
24
|
+
declare const stateRevisionBrand: unique symbol;
|
|
25
|
+
declare const topologyRevisionBrand: unique symbol;
|
|
26
|
+
type DiagnosticSessionId = string & {
|
|
27
|
+
readonly [sessionIdBrand]?: true;
|
|
28
|
+
};
|
|
29
|
+
type DiagnosticNodeId = string & {
|
|
30
|
+
readonly [nodeIdBrand]?: true;
|
|
31
|
+
};
|
|
32
|
+
type DiagnosticEdgeId = string & {
|
|
33
|
+
readonly [edgeIdBrand]?: true;
|
|
34
|
+
};
|
|
35
|
+
type DiagnosticConditionId = string & {
|
|
36
|
+
readonly [conditionIdBrand]?: true;
|
|
37
|
+
};
|
|
38
|
+
type DiagnosticPlanFingerprint = string & {
|
|
39
|
+
readonly [planFingerprintBrand]?: true;
|
|
40
|
+
};
|
|
41
|
+
type DiagnosticSequence = number & {
|
|
42
|
+
readonly [sequenceBrand]?: true;
|
|
43
|
+
};
|
|
44
|
+
type DiagnosticStateRevision = number & {
|
|
45
|
+
readonly [stateRevisionBrand]?: true;
|
|
46
|
+
};
|
|
47
|
+
type DiagnosticTopologyRevision = number & {
|
|
48
|
+
readonly [topologyRevisionBrand]?: true;
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Non-negative integer epoch milliseconds within Date's range, up to 8_640_000_000_000_000 inclusive.
|
|
52
|
+
* The protocol has exactly one clock and it belongs to `DiagnosticCauseRef`: it stamps the
|
|
53
|
+
* trigger of a commit, not the commit itself, and no duration is derived from it. State records therefore carry no
|
|
54
|
+
* timestamps of their own — a second clock would let the panel invent spans the runtime never measured.
|
|
55
|
+
*/
|
|
56
|
+
type DiagnosticTimestamp = number;
|
|
57
|
+
/**
|
|
58
|
+
* Human-readable text the compiler derived from declarations. Presentation only: search may index it, control must
|
|
59
|
+
* never accept it as a target (§2.6).
|
|
60
|
+
*/
|
|
61
|
+
type DiagnosticLabel = string;
|
|
62
|
+
/** Project-relative provenance of a declaration. Never an execution identity (§2.6). */
|
|
63
|
+
type DiagnosticProvenance = string;
|
|
64
|
+
declare class DevtoolsProtocolError extends Error {
|
|
65
|
+
readonly code: 'invalid-id' | 'invalid-revision' | 'invalid-sequence';
|
|
66
|
+
constructor(code: 'invalid-id' | 'invalid-revision' | 'invalid-sequence', message: string);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* How long an id may be, derived from the producer's own laws rather than chosen (D200, §2.6).
|
|
70
|
+
*
|
|
71
|
+
* An atom is a declaration id: at most 160 characters over `[A-Za-z0-9./:-]`, where a separator always sits between
|
|
72
|
+
* two alphanumerics, so at most 79 of them. Escaping turns `/` and `:` into two characters each, and that is the
|
|
73
|
+
* only growth an atom can suffer.
|
|
74
|
+
*
|
|
75
|
+
* A node id is a prefix and one escaped atom — `contribution:` is the longest prefix the producer writes. A group is
|
|
76
|
+
* the exception: its key joins the ids of its conditions, so a set large enough to outgrow one escaped atom is
|
|
77
|
+
* refused as an oversized id. That is a budget rather than a law, and it is the same kind of refusal `maxNodes` is.
|
|
78
|
+
*
|
|
79
|
+
* An edge id is a kind and its two endpoints escaped again, which doubles every escape the endpoints already carry.
|
|
80
|
+
*/
|
|
81
|
+
declare const maxAtomId = 160;
|
|
82
|
+
declare const maxEncodedAtom: number;
|
|
83
|
+
declare const maxNodeId: number;
|
|
84
|
+
declare const maxEdgeId: number;
|
|
85
|
+
declare function diagnosticSessionId(value: string): DiagnosticSessionId;
|
|
86
|
+
declare function diagnosticNodeId(value: string, limit?: number): DiagnosticNodeId;
|
|
87
|
+
declare function diagnosticEdgeId(value: string, limit?: number): DiagnosticEdgeId;
|
|
88
|
+
/**
|
|
89
|
+
* A condition id is one escaped atom: the producer writes `encodeIdPart(declarationId)` and addresses control by it,
|
|
90
|
+
* while the readable original stays in the node's label (D200). Escaping is the only growth it can suffer, so it is
|
|
91
|
+
* bounded by the encoded atom rather than by the declaration id it came from.
|
|
92
|
+
*/
|
|
93
|
+
declare function diagnosticConditionId(value: string): DiagnosticConditionId;
|
|
94
|
+
declare function diagnosticPlanFingerprint(value: string): DiagnosticPlanFingerprint;
|
|
95
|
+
declare function diagnosticSequence(value: number): DiagnosticSequence;
|
|
96
|
+
declare function diagnosticStateRevision(value: number): DiagnosticStateRevision;
|
|
97
|
+
declare function diagnosticTopologyRevision(value: number): DiagnosticTopologyRevision;
|
|
98
|
+
/**
|
|
99
|
+
* The node kinds of the compiled plan and of the live world, named with the public words of `spec.md` §3: a
|
|
100
|
+
* feature, one instance of it, the condition group that owns its activation, the port it requires or provides, the
|
|
101
|
+
* host contract the application binds, the target it contributes to and the contribution itself.
|
|
102
|
+
*
|
|
103
|
+
* Projection hides kinds; it never renames them into generic "nodes".
|
|
104
|
+
*/
|
|
105
|
+
type DiagnosticNodeKind = 'absent-provider' | 'application' | 'attachment' | 'condition' | 'condition-group' | 'contribution' | 'contribution-target' | 'feature' | 'feature-instance' | 'host-contract' | 'port';
|
|
106
|
+
/**
|
|
107
|
+
* A hard edge: the provider opens before the consumer, the consumer closes before the provider, and the compiler
|
|
108
|
+
* refuses the edge unless the provider's condition set is a subset of the consumer's (`spec.md` §4). These are the
|
|
109
|
+
* only edges an activation can wait on.
|
|
110
|
+
*/
|
|
111
|
+
type HardEdgeKind = 'hard-import' | 'requires-port';
|
|
112
|
+
/**
|
|
113
|
+
* A weak edge: one word, `optional`, for features and for ports (D105). The consumer may outlive the provider, reads
|
|
114
|
+
* a `Lookup` projection instead of a value, and never waits for it. `absent-import` and `absent-port` are the same
|
|
115
|
+
* edge with no provider in the plan at all — the runtime value is identical, which is exactly D105's point, and only
|
|
116
|
+
* the plan remembers the difference.
|
|
117
|
+
*/
|
|
118
|
+
type WeakEdgeKind = 'absent-import' | 'absent-port' | 'optional-import' | 'optional-port';
|
|
119
|
+
/**
|
|
120
|
+
* Edges that carry no state of their own. `provides-port` names the single provider the compiler selected,
|
|
121
|
+
* `condition-control` names a condition that gates a group, `owns` names the lifetime owner of a feature, and
|
|
122
|
+
* `on-demand-import` is a host contract whose connection is deferred to the attachment that opens it, so it is
|
|
123
|
+
* neither a wait nor a `Lookup`.
|
|
124
|
+
*/
|
|
125
|
+
type StructuralEdgeKind = 'condition-control' | 'contributes-to' | 'on-demand-import' | 'owns' | 'provides-port';
|
|
126
|
+
type DiagnosticEdgeKind = HardEdgeKind | StructuralEdgeKind | WeakEdgeKind;
|
|
127
|
+
/** What a weak edge reports to its consumer right now: literally the `Lookup` the projection yields (D105). */
|
|
128
|
+
type WeakEdgeState = 'found' | 'missing';
|
|
129
|
+
type DiagnosticEdgeBase = Readonly<{
|
|
130
|
+
from: DiagnosticNodeId;
|
|
131
|
+
id: DiagnosticEdgeId;
|
|
132
|
+
to: DiagnosticNodeId;
|
|
133
|
+
}>;
|
|
134
|
+
/**
|
|
135
|
+
* Orientation is fixed: provider, blocker or owner → consumer, waiter or owned node. The panel may print
|
|
136
|
+
* "B depends on A"; it must not flip the data.
|
|
137
|
+
*
|
|
138
|
+
* State lives on the edge kinds that have one, so a weak edge cannot claim to be pending and a hard edge cannot
|
|
139
|
+
* claim a `Lookup`.
|
|
140
|
+
*/
|
|
141
|
+
type DiagnosticEdge = (DiagnosticEdgeBase & Readonly<{
|
|
142
|
+
kind: HardEdgeKind;
|
|
143
|
+
pending: boolean;
|
|
144
|
+
}>) | (DiagnosticEdgeBase & Readonly<{
|
|
145
|
+
kind: StructuralEdgeKind;
|
|
146
|
+
}>) | (DiagnosticEdgeBase & Readonly<{
|
|
147
|
+
kind: WeakEdgeKind;
|
|
148
|
+
lookup: WeakEdgeState;
|
|
149
|
+
}>);
|
|
150
|
+
/** A hard edge is the only thing an activation waits on, so only these can hold up a feature instance. */
|
|
151
|
+
declare function isHardEdge(edge: DiagnosticEdge): edge is DiagnosticEdgeBase & {
|
|
152
|
+
kind: HardEdgeKind;
|
|
153
|
+
pending: boolean;
|
|
154
|
+
};
|
|
155
|
+
declare function isWeakEdge(edge: DiagnosticEdge): edge is DiagnosticEdgeBase & {
|
|
156
|
+
kind: WeakEdgeKind;
|
|
157
|
+
lookup: WeakEdgeState;
|
|
158
|
+
};
|
|
159
|
+
/** Both kinds of dependency edge, which is what the inspector lists as "blocked by" (weak rows read `missing`). */
|
|
160
|
+
declare function isDependencyEdge(edge: DiagnosticEdge): boolean;
|
|
161
|
+
/** Axis one: what the machine wants, before and after a debug override that no build can set yet (§5.4). */
|
|
162
|
+
type DesiredAxis = 'active' | 'inactive';
|
|
163
|
+
type DiagnosticOverride = 'force-inactive' | 'none';
|
|
164
|
+
/**
|
|
165
|
+
* Axis two: where the node sits in its own automaton. `declared` is the seventh value: a feature the plan declares
|
|
166
|
+
* while no instance of it is open is in none of the other six, and calling it `waiting` would claim a blocker that
|
|
167
|
+
* does not exist.
|
|
168
|
+
*/
|
|
169
|
+
type LifecycleAxis = 'closed' | 'declared' | 'fencing' | 'opening' | 'ready' | 'retiring' | 'waiting';
|
|
170
|
+
/** Axis three: whether that position is a problem. */
|
|
171
|
+
type HealthAxis = 'blocked' | 'cancelled' | 'failed' | 'healthy' | 'quarantined' | 'stale';
|
|
172
|
+
/**
|
|
173
|
+
* When a host contract is connected: with the instance that owns it, or lazily on first use. The literal is the
|
|
174
|
+
* runtime's own `ImportDemand` from `runtime/src/feature-contract.ts` and is declared once here, so following the
|
|
175
|
+
* runtime is one edit rather than three.
|
|
176
|
+
*/
|
|
177
|
+
type HostContractDemand = 'instance' | 'onDemand';
|
|
178
|
+
/** The phase vocabulary of D14, unchanged: one word set for every lifecycle transaction in the runtime. */
|
|
179
|
+
type DiagnosticPhase = 'close' | 'drain' | 'fence' | 'open' | 'retry' | 'rollback';
|
|
180
|
+
/**
|
|
181
|
+
* The whole vocabulary of failure the protocol exports, and it is the runtime's own: one class per subject of the
|
|
182
|
+
* author vocabulary with the state in `code` (`spec.md` §4, D69). Subject and code are one discriminated union, so a
|
|
183
|
+
* `ContributionError` code can never be reported under `FeatureError`. Arbitrary error text stays out (§2.6).
|
|
184
|
+
*/
|
|
185
|
+
type DiagnosticFailureCode = Readonly<{
|
|
186
|
+
code: 'binding-invalid' | 'duplicate' | 'inactive' | 'missing';
|
|
187
|
+
subject: 'contribution';
|
|
188
|
+
}> | Readonly<{
|
|
189
|
+
code: 'cancelled' | 'closed' | 'publication-rejected' | 'unavailable';
|
|
190
|
+
subject: 'call';
|
|
191
|
+
}> | Readonly<{
|
|
192
|
+
code: 'cleanup-failed' | 'not-ready' | 'quarantined' | 'retired';
|
|
193
|
+
subject: 'feature';
|
|
194
|
+
}> | Readonly<{
|
|
195
|
+
code: 'closed';
|
|
196
|
+
subject: 'application';
|
|
197
|
+
}> | Readonly<{
|
|
198
|
+
code: 'closed';
|
|
199
|
+
subject: 'readable';
|
|
200
|
+
}> | Readonly<{
|
|
201
|
+
code: 'invalid-id';
|
|
202
|
+
subject: 'declaration';
|
|
203
|
+
}>;
|
|
204
|
+
type DiagnosticFailure = DiagnosticFailureCode & Readonly<{
|
|
205
|
+
cause?: DiagnosticCauseRef;
|
|
206
|
+
phase: DiagnosticPhase;
|
|
207
|
+
}>;
|
|
208
|
+
/**
|
|
209
|
+
* Why a feature instance has not opened yet. Typed, so the panel never infers a reason from a neighbour's colour.
|
|
210
|
+
* `awaiting-turn` is the honest name for the canonical order: members of a group open one at a time in
|
|
211
|
+
* `plan.groups[].order`, so a member can be blocked by nothing but its own position.
|
|
212
|
+
*/
|
|
213
|
+
type DiagnosticWaitReason = 'awaiting-condition' | 'awaiting-hard-import' | 'awaiting-port' | 'awaiting-turn';
|
|
214
|
+
type DiagnosticClosedReason = 'application-closed' | 'condition-inactive' | 'debug-override' | 'plan-replacement' | 'retired';
|
|
215
|
+
type DiagnosticCauseKind = 'activity-change' | 'condition-change' | 'contribution-change' | 'control-command' | 'failure' | 'group-transition' | 'instance-transition' | 'plan-replacement' | 'session-reset';
|
|
216
|
+
/**
|
|
217
|
+
* A ref, not an explanation: the panel resolves it against the snapshot instead of parsing prose. `at` stamps the
|
|
218
|
+
* trigger and is the protocol's only clock; a commit with no cause has no time, and the timeline shows that column
|
|
219
|
+
* empty rather than inventing one.
|
|
220
|
+
*/
|
|
221
|
+
type DiagnosticCauseRef = Readonly<{
|
|
222
|
+
at?: DiagnosticTimestamp;
|
|
223
|
+
condition?: DiagnosticConditionId;
|
|
224
|
+
kind: DiagnosticCauseKind;
|
|
225
|
+
node?: DiagnosticNodeId;
|
|
226
|
+
sequence?: DiagnosticSequence;
|
|
227
|
+
}>;
|
|
228
|
+
/**
|
|
229
|
+
* What a condition group wants. `source` is the conjunction of its conditions, `override` is the debug override no
|
|
230
|
+
* build can set yet, and `effective` is what the activation machine acts on.
|
|
231
|
+
*/
|
|
232
|
+
type DiagnosticDesiredState = Readonly<{
|
|
233
|
+
effective: DesiredAxis;
|
|
234
|
+
override: DiagnosticOverride;
|
|
235
|
+
source: DesiredAxis;
|
|
236
|
+
}>;
|
|
237
|
+
export { DevtoolsProtocolError, diagnosticConditionId, diagnosticEdgeId, diagnosticNodeId, diagnosticPlanFingerprint, diagnosticSequence, diagnosticSessionId, diagnosticStateRevision, diagnosticTopologyRevision, isDependencyEdge, isHardEdge, isWeakEdge, maxAtomId, maxEdgeId, maxEncodedAtom, maxNodeId, };
|
|
238
|
+
export type { DesiredAxis, DiagnosticCauseKind, DiagnosticCauseRef, DiagnosticClosedReason, DiagnosticConditionId, DiagnosticDesiredState, DiagnosticEdge, DiagnosticEdgeId, DiagnosticEdgeKind, DiagnosticFailure, DiagnosticFailureCode, DiagnosticLabel, DiagnosticNodeId, DiagnosticNodeKind, DiagnosticOverride, DiagnosticPhase, DiagnosticPlanFingerprint, DiagnosticProvenance, DiagnosticSequence, DiagnosticSessionId, DiagnosticStateRevision, DiagnosticTimestamp, DiagnosticTopologyRevision, DiagnosticWaitReason, HardEdgeKind, HealthAxis, HostContractDemand, LifecycleAxis, StructuralEdgeKind, WeakEdgeKind, WeakEdgeState, };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ActivityOwner, RuntimeActivitySnapshot } from '../activity.js';
|
|
2
|
+
type ActivityRow = Readonly<{
|
|
3
|
+
details: readonly (readonly [string, number | string])[];
|
|
4
|
+
id: string;
|
|
5
|
+
kind: 'call' | 'feature' | 'resource' | 'stream';
|
|
6
|
+
label: string;
|
|
7
|
+
owner: ActivityOwner;
|
|
8
|
+
state: string;
|
|
9
|
+
}>;
|
|
10
|
+
declare function activityRows(snapshot: RuntimeActivitySnapshot): ActivityRow[];
|
|
11
|
+
export { activityRows };
|
|
12
|
+
export type { ActivityRow };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import type { InspectorProjection } from '../inspector.js';
|
|
3
|
+
import type { DiagnosticNodeId } from '../protocol.js';
|
|
4
|
+
type ActivityViewProps = Readonly<{
|
|
5
|
+
onSelect: (id: DiagnosticNodeId) => void;
|
|
6
|
+
projection: InspectorProjection;
|
|
7
|
+
}>;
|
|
8
|
+
declare function ActivityView({ onSelect, projection }: ActivityViewProps): ReactElement;
|
|
9
|
+
export { ActivityView };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control surface's own dictionary, kept out of the panel's (§5).
|
|
3
|
+
*
|
|
4
|
+
* A build that never receives a write port should not ship the words for buttons it will never draw, so these words
|
|
5
|
+
* live in the lazy chunk with the code that uses them. The discipline is the panel's: English is the source, the
|
|
6
|
+
* Russian record is typed against it, and a missing key is a type error rather than an English word in a Russian
|
|
7
|
+
* panel. The protocol's own vocabulary — the command names, `applied`, `rejected`, the rejection reasons and the
|
|
8
|
+
* override value — is not translated in either.
|
|
9
|
+
*/
|
|
10
|
+
import type { DevtoolsLocale } from './locale.js';
|
|
11
|
+
declare const en: {
|
|
12
|
+
controlActions: string;
|
|
13
|
+
controlApplied: (command: string, target: string, sequence: number) => string;
|
|
14
|
+
controlAudit: string;
|
|
15
|
+
controlCancel: string;
|
|
16
|
+
controlConfirm: string;
|
|
17
|
+
controlConfirmQuestion: (action: string, target: string) => string;
|
|
18
|
+
controlRejected: (command: string, target: string, reason: string) => string;
|
|
19
|
+
controlResume: string;
|
|
20
|
+
controlRetryCleanup: string;
|
|
21
|
+
controlSuspend: string;
|
|
22
|
+
};
|
|
23
|
+
type ControlStrings = typeof en;
|
|
24
|
+
declare function controlStringsFor(locale: DevtoolsLocale): ControlStrings;
|
|
25
|
+
export { controlStringsFor };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control surface's own rules, injected when its chunk lands (§5). They sit here rather than in the panel's
|
|
3
|
+
* stylesheet for the same reason its dictionary does: a panel installed without a write port should carry neither
|
|
4
|
+
* the code nor the bytes of a surface it will never draw.
|
|
5
|
+
*/
|
|
6
|
+
declare function ensureControlStylesheet(): void;
|
|
7
|
+
export { ensureControlStylesheet };
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The control surface of `docs/devtools.md` §5, and the only part of the panel that can write to the application.
|
|
3
|
+
*
|
|
4
|
+
* It is a chunk of its own, loaded when a host passes a control session and never otherwise. That is not only a size
|
|
5
|
+
* decision: a build with no write port must not carry the code that would use one, so a panel that never loaded this
|
|
6
|
+
* module is read-only by construction rather than by a disabled button.
|
|
7
|
+
*
|
|
8
|
+
* Every command asks for confirmation first. Suspending a condition stops a real application, so the button that
|
|
9
|
+
* does it is deliberately two presses away, and the second press names what it is about to do.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactElement } from 'react';
|
|
12
|
+
import type { ControlAuditEntry, RuntimeControlSession } from '../control.js';
|
|
13
|
+
import type { DevtoolsGraphStore } from '../graph-store.js';
|
|
14
|
+
import type { InspectorProjection } from '../inspector.js';
|
|
15
|
+
type ControlSlotProps = Readonly<{
|
|
16
|
+
audit: readonly ControlAuditEntry[];
|
|
17
|
+
onAudit: (entry: ControlAuditEntry) => void;
|
|
18
|
+
/** The bar over the canvas carries the group's commands; the inspector carries those and the override badge. */
|
|
19
|
+
place: 'bar' | 'inspector';
|
|
20
|
+
projection: InspectorProjection | undefined;
|
|
21
|
+
session: RuntimeControlSession;
|
|
22
|
+
store: DevtoolsGraphStore;
|
|
23
|
+
}>;
|
|
24
|
+
/**
|
|
25
|
+
* What may be controlled about the selected node, and nothing else (§5.1): a condition group through its conditions,
|
|
26
|
+
* and a quarantined instance through the exact frontier its cleanup left. Everything else has no command at all, so
|
|
27
|
+
* the slot renders nothing rather than a disabled button that would have to explain itself.
|
|
28
|
+
*/
|
|
29
|
+
declare function ControlSlot({ audit, onAudit, place, projection, session, store, }: ControlSlotProps): ReactElement | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* The panel's own audit trail, above the runtime's timeline and visibly not part of it: an applied command points at
|
|
32
|
+
* the frame that carries it, and a rejected one has no frame to point at, because nothing was committed.
|
|
33
|
+
*/
|
|
34
|
+
declare function ControlAudit({ entries }: {
|
|
35
|
+
readonly entries: readonly ControlAuditEntry[];
|
|
36
|
+
}): ReactElement | undefined;
|
|
37
|
+
export { ControlAudit, ControlSlot };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the `Graph` tab draws, as the developer decides it: one chip per protocol edge kind, and presets that are
|
|
3
|
+
* named sets of those chips (`docs/devtools.md` §4.2).
|
|
4
|
+
*
|
|
5
|
+
* The five lenses this replaces were five answers to one question — which relations am I looking at — and a
|
|
6
|
+
* developer who wanted two of them at once had to choose. A chip set is the same choice made additive, so the
|
|
7
|
+
* presets are shorthand rather than modes: the state is the set of enabled kinds, and a preset is just the name that
|
|
8
|
+
* set happens to have.
|
|
9
|
+
*/
|
|
10
|
+
import type { DiagnosticEdgeKind } from '../protocol.js';
|
|
11
|
+
/** The two tabs of §4.1. Everything that used to be a tab is a filter inside `graph`. */
|
|
12
|
+
type DevtoolsTab = 'graph' | 'timeline';
|
|
13
|
+
type EdgePresetId = 'conditions' | 'extension-points' | 'imports-and-ports' | 'ownership';
|
|
14
|
+
type EdgePreset = Readonly<{
|
|
15
|
+
id: EdgePresetId;
|
|
16
|
+
kinds: readonly DiagnosticEdgeKind[];
|
|
17
|
+
/** Key into the string dictionary: a preset name is prose, and prose is translated. */
|
|
18
|
+
label: 'presetConditions' | 'presetExtensionPoints' | 'presetImportsAndPorts' | 'presetOwnership';
|
|
19
|
+
}>;
|
|
20
|
+
/**
|
|
21
|
+
* Chip order is the protocol's own grouping — imports, then ports, then the structural relations — so a developer
|
|
22
|
+
* reading the row finds a kind where its neighbours are. The labels are protocol vocabulary and stay untranslated.
|
|
23
|
+
*/
|
|
24
|
+
declare const edgeKinds: readonly DiagnosticEdgeKind[];
|
|
25
|
+
declare const edgePresets: readonly EdgePreset[];
|
|
26
|
+
declare const defaultPreset: EdgePresetId;
|
|
27
|
+
declare function presetKinds(id: EdgePresetId): ReadonlySet<DiagnosticEdgeKind>;
|
|
28
|
+
/**
|
|
29
|
+
* The name of the current selection, or `undefined` when it has none. Derived rather than stored: a chip toggled
|
|
30
|
+
* back into a preset's exact set is that preset again, and a panel that remembered otherwise would be lying about
|
|
31
|
+
* what it draws.
|
|
32
|
+
*/
|
|
33
|
+
declare function presetOf(selected: ReadonlySet<DiagnosticEdgeKind>): EdgePresetId | undefined;
|
|
34
|
+
declare function toggleKind(selected: ReadonlySet<DiagnosticEdgeKind>, kind: DiagnosticEdgeKind): ReadonlySet<DiagnosticEdgeKind>;
|
|
35
|
+
export { defaultPreset, edgeKinds, edgePresets, presetKinds, presetOf, toggleKind };
|
|
36
|
+
export type { DevtoolsTab, EdgePreset, EdgePresetId };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The filter row of `docs/devtools.md` §4.2: four presets on the left, one chip per edge kind on the right.
|
|
3
|
+
*
|
|
4
|
+
* A preset is a button and not a tab, because pressing it is not a mode switch — it writes the chips and leaves.
|
|
5
|
+
* The chips are the state, so the pressed preset is whichever one currently matches them, and none is pressed while
|
|
6
|
+
* the developer holds a set of their own.
|
|
7
|
+
*/
|
|
8
|
+
import type { ReactElement } from 'react';
|
|
9
|
+
import type { DiagnosticEdgeKind } from '../protocol.js';
|
|
10
|
+
import type { EdgePresetId } from './edge-filter.js';
|
|
11
|
+
type EdgeFiltersProps = Readonly<{
|
|
12
|
+
onPreset: (preset: EdgePresetId) => void;
|
|
13
|
+
onToggle: (kind: DiagnosticEdgeKind) => void;
|
|
14
|
+
selected: ReadonlySet<DiagnosticEdgeKind>;
|
|
15
|
+
}>;
|
|
16
|
+
declare function EdgeFilters({ onPreset, onToggle, selected }: EdgeFiltersProps): ReactElement;
|
|
17
|
+
export { EdgeFilters };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delivery of a snapshot export to wherever the host can actually receive it.
|
|
3
|
+
*
|
|
4
|
+
* §2.6 allows no network transport, so there are exactly two destinations: the clipboard, and a file the browser saves
|
|
5
|
+
* locally. Which one is available depends on the host — the clipboard needs a secure context and a permission the
|
|
6
|
+
* user may refuse — so both are tried in order and the outcome is reported rather than assumed. A button that
|
|
7
|
+
* silently did nothing is the defect this replaces.
|
|
8
|
+
*/
|
|
9
|
+
type ExportOutcome = Readonly<{
|
|
10
|
+
detail?: string;
|
|
11
|
+
kind: 'copied' | 'downloaded' | 'failed' | 'handled';
|
|
12
|
+
}>;
|
|
13
|
+
/**
|
|
14
|
+
* The capabilities delivery needs, named one by one so a test can offer a host with only some of them, and so no
|
|
15
|
+
* global is read outside `browserExportHost`.
|
|
16
|
+
*/
|
|
17
|
+
type ExportHost = Readonly<{
|
|
18
|
+
download?: (text: string, filename: string) => void;
|
|
19
|
+
writeText?: (text: string) => PromiseLike<void>;
|
|
20
|
+
}>;
|
|
21
|
+
/** Ids carry dots and colons; a filename should carry neither. */
|
|
22
|
+
declare function exportFilename(sessionId: string, sequence: number): string;
|
|
23
|
+
/** What this browser can do, decided by feature detection rather than by a user-agent guess. */
|
|
24
|
+
declare function browserExportHost(): ExportHost;
|
|
25
|
+
/**
|
|
26
|
+
* Clipboard first, because the developer is usually about to paste it into a report; a saved file second, because a
|
|
27
|
+
* large graph is easier to keep than to hold. Failure is a reported outcome, never a thrown error: an export that
|
|
28
|
+
* could not be delivered must not take the panel down with it.
|
|
29
|
+
*/
|
|
30
|
+
declare function deliverExport(text: string, filename: string, host: ExportHost): Promise<ExportOutcome>;
|
|
31
|
+
/** The outcome is reported in the panel's language, so the key is resolved against the dictionary, not hard-coded. */
|
|
32
|
+
export { browserExportHost, deliverExport, exportFilename };
|
|
33
|
+
export type { ExportHost, ExportOutcome };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Running an export, and the words that report where it went (`docs/devtools.md` §2.6).
|
|
3
|
+
*
|
|
4
|
+
* The panel keeps the button; this module is what the button loads. Building a redacted snapshot, canonicalising it
|
|
5
|
+
* and reaching a clipboard is a page of code and four sentences of prose that a session which never exports has no
|
|
6
|
+
* reason to carry, so both travel behind the click.
|
|
7
|
+
*/
|
|
8
|
+
import type { DevtoolsExport } from '../export-snapshot.js';
|
|
9
|
+
import { exportSnapshot } from '../export-snapshot.js';
|
|
10
|
+
import type { ExportHost } from './export-delivery.js';
|
|
11
|
+
import type { DevtoolsLocale } from './locale.js';
|
|
12
|
+
type ExportRequest = Readonly<{
|
|
13
|
+
host: ExportHost | undefined;
|
|
14
|
+
locale: DevtoolsLocale;
|
|
15
|
+
onExport: ((payload: DevtoolsExport) => void) | undefined;
|
|
16
|
+
store: Parameters<typeof exportSnapshot>[0];
|
|
17
|
+
}>;
|
|
18
|
+
/** §2.6: no network transport. The clipboard and a local file are the only destinations delivery may use. */
|
|
19
|
+
declare function runExport({ host, locale, onExport, store }: ExportRequest): Promise<string>;
|
|
20
|
+
export { runExport };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Presentation-only formatting. Nothing here decides state; it renders values the projection already resolved. */
|
|
2
|
+
import type { DiagnosticTimestamp } from '../protocol.js';
|
|
3
|
+
import type { Strings } from './locale.js';
|
|
4
|
+
/** `14:02:31` — wall-clock only, for facts where the millisecond adds noise rather than ordering. */
|
|
5
|
+
declare function formatClock(at: DiagnosticTimestamp): string;
|
|
6
|
+
/** `14:02:31.114` — the timestamp column of the causal chain and the timeline, where ordering is the point. */
|
|
7
|
+
declare function formatTime(at: DiagnosticTimestamp): string;
|
|
8
|
+
/**
|
|
9
|
+
* `3 current · 2 previous` — a feature declaration outlives its instances, so the counter says which life this is.
|
|
10
|
+
* The number of the previous life carries no time: the protocol's only clock is `cause.at`, and it stamps a trigger,
|
|
11
|
+
* not a retirement.
|
|
12
|
+
*/
|
|
13
|
+
declare function formatInstance(generation: number, previous: number | undefined, strings: Strings): string;
|
|
14
|
+
/** The time column of a commit that has no cause is empty, because the protocol never stamped one. */
|
|
15
|
+
declare function formatOptionalTime(at: DiagnosticTimestamp | undefined): string;
|
|
16
|
+
declare function formatMilliseconds(value: number): string;
|
|
17
|
+
export { formatClock, formatInstance, formatMilliseconds, formatOptionalTime, formatTime };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State coding per `docs/devtools.md` §4.6: shape, colour and text tag together, never colour alone.
|
|
3
|
+
*
|
|
4
|
+
* The glyph itself is `aria-hidden`: it is always rendered next to the state's own text — a tag in the tree and the
|
|
5
|
+
* inspector, the sub-line on a graph card — so a screen reader gets the state once, as words.
|
|
6
|
+
*/
|
|
7
|
+
import type { ReactElement } from 'react';
|
|
8
|
+
import type { NodeDisplayState } from '../visual-axes.js';
|
|
9
|
+
import type { Strings } from './locale.js';
|
|
10
|
+
declare function stateDescription(state: NodeDisplayState, strings: Strings): string;
|
|
11
|
+
/** Every lifecycle state the panel can draw, in the order the legend lists them. */
|
|
12
|
+
declare const displayStates: readonly NodeDisplayState[];
|
|
13
|
+
declare function StateGlyph({ size, state }: {
|
|
14
|
+
size?: number;
|
|
15
|
+
state: NodeDisplayState;
|
|
16
|
+
}): ReactElement;
|
|
17
|
+
declare function StateTag({ state }: {
|
|
18
|
+
state: NodeDisplayState;
|
|
19
|
+
}): ReactElement;
|
|
20
|
+
export { displayStates, stateDescription, StateGlyph, StateTag };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic three-column layout for the selected node's neighbourhood: blockers on the left, the selection in
|
|
3
|
+
* the middle, what it owns and publishes on the right — the arrangement `docs/devtools.md` §4 describes.
|
|
4
|
+
*
|
|
5
|
+
* Layout is pure and measured in CSS pixels, and the SVG that draws the edges uses the same pixel box as the layer
|
|
6
|
+
* holding the cards. That is what keeps edges attached to cards at any drawer height (§8 acceptance), and it keeps
|
|
7
|
+
* the layout testable and cacheable by topology revision (§6) without a DOM.
|
|
8
|
+
*/
|
|
9
|
+
import type { ExtensionPointsProjection } from '../extension-points.js';
|
|
10
|
+
import type { InspectorProjection } from '../inspector.js';
|
|
11
|
+
import type { DiagnosticEdgeKind, DiagnosticLabel, DiagnosticNodeId } from '../protocol.js';
|
|
12
|
+
import type { NodeDisplayState } from '../visual-axes.js';
|
|
13
|
+
import type { Strings } from './locale.js';
|
|
14
|
+
declare const cardWidth = 188;
|
|
15
|
+
type LaidOutNode = Readonly<{
|
|
16
|
+
/** Third line of the selected card: what its current generation owns. */
|
|
17
|
+
detail?: string;
|
|
18
|
+
display: NodeDisplayState;
|
|
19
|
+
height: number;
|
|
20
|
+
id: DiagnosticNodeId;
|
|
21
|
+
/**
|
|
22
|
+
* React key. It cannot be the node id: one provider can reach the same consumer through two relations — a
|
|
23
|
+
* candidate admission and a selected-provider wait, say — and then appears in the column twice.
|
|
24
|
+
*/
|
|
25
|
+
key: string;
|
|
26
|
+
label: DiagnosticLabel;
|
|
27
|
+
selected: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* The `<relation> · <state>` line under the title, already split so the card does not build strings. Absent on the
|
|
30
|
+
* selected card, which carries its kind and generation as tags and its state as a state tag instead.
|
|
31
|
+
*/
|
|
32
|
+
sub?: readonly [string, string];
|
|
33
|
+
/** Tag chips on the selected card: its kind and its generation. */
|
|
34
|
+
tags?: readonly string[];
|
|
35
|
+
/** The full name, for `title`: `label` may have been shortened to fit the card, and this never is (§4.3). */
|
|
36
|
+
title: DiagnosticLabel;
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
}>;
|
|
40
|
+
type LaidOutEdge = Readonly<{
|
|
41
|
+
id: string;
|
|
42
|
+
kind: DiagnosticEdgeKind;
|
|
43
|
+
path: string;
|
|
44
|
+
}>;
|
|
45
|
+
type GraphLayout = Readonly<{
|
|
46
|
+
edges: readonly LaidOutEdge[];
|
|
47
|
+
height: number;
|
|
48
|
+
/** Relations the filter leaves out. Shown as a hint, so an empty canvas is never mistaken for an isolated node. */
|
|
49
|
+
hidden: number;
|
|
50
|
+
nodes: readonly LaidOutNode[];
|
|
51
|
+
width: number;
|
|
52
|
+
}>;
|
|
53
|
+
/**
|
|
54
|
+
* Lays out the neighbourhood the filter is interested in. Nested owned rows are flattened into the right column: the
|
|
55
|
+
* canvas shows one hop, the inspector shows the ownership tree.
|
|
56
|
+
*/
|
|
57
|
+
declare function layoutNeighbourhood(projection: InspectorProjection, edgeKinds: ReadonlySet<DiagnosticEdgeKind> | undefined, strings: Strings): GraphLayout;
|
|
58
|
+
/**
|
|
59
|
+
* The `Extension points` picture: the selection on the left, every contribution that reaches the same targets in the
|
|
60
|
+
* middle, and those targets on the right — inverted into contributors when a target is what was selected.
|
|
61
|
+
*
|
|
62
|
+
* Sibling contributions are drawn without an edge back to the selection, because they have none: they compete for
|
|
63
|
+
* the same target, which is the whole reason they are on the canvas.
|
|
64
|
+
*/
|
|
65
|
+
declare function layoutExtensionPoints(projection: ExtensionPointsProjection): GraphLayout;
|
|
66
|
+
export { cardWidth, layoutExtensionPoints, layoutNeighbourhood };
|
|
67
|
+
export type { GraphLayout, LaidOutEdge, LaidOutNode };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The graph canvas of `docs/devtools.md` §4, and the list that replaces it on narrow widths (§4.4).
|
|
3
|
+
*
|
|
4
|
+
* The SVG shares one pixel box with the layer holding the cards and uses `preserveAspectRatio="xMinYMin meet"`, so
|
|
5
|
+
* edges stay attached to cards at any zoom — that is the §8 acceptance criterion for this view. The camera moves the
|
|
6
|
+
* layer, never the coordinates, so the layout stays cacheable and edges never drift.
|
|
7
|
+
*
|
|
8
|
+
* The canvas clips its content: a lens may not draw outside itself. Reaching a node that the fit did not cover is
|
|
9
|
+
* therefore pan and zoom, which is what the surrounding column owns.
|
|
10
|
+
*/
|
|
11
|
+
import type { ReactElement, RefObject } from 'react';
|
|
12
|
+
import type { DiagnosticNodeId } from '../protocol.js';
|
|
13
|
+
import type { GraphLayout } from './graph-layout.js';
|
|
14
|
+
import type { CanvasTransform, ViewportPoint } from './graph-viewport.js';
|
|
15
|
+
type GraphViewProps = Readonly<{
|
|
16
|
+
canvasRef: RefObject<HTMLDivElement | null>;
|
|
17
|
+
layout: GraphLayout;
|
|
18
|
+
onPan: (dx: number, dy: number) => void;
|
|
19
|
+
onSelect: (id: DiagnosticNodeId) => void;
|
|
20
|
+
onZoomAt: (point: ViewportPoint, factor: number) => void;
|
|
21
|
+
transform: CanvasTransform;
|
|
22
|
+
}>;
|
|
23
|
+
declare function GraphView({ canvasRef, layout, onPan, onSelect, onZoomAt, transform }: GraphViewProps): ReactElement;
|
|
24
|
+
type NeighbourListProps = Readonly<{
|
|
25
|
+
layout: GraphLayout;
|
|
26
|
+
onSelect: (id: DiagnosticNodeId) => void;
|
|
27
|
+
}>;
|
|
28
|
+
/** The same neighbourhood as a list: the graph's fallback on narrow widths, and a keyboard-first way to walk it. */
|
|
29
|
+
declare function NeighbourList({ layout, onSelect }: NeighbourListProps): ReactElement;
|
|
30
|
+
export { GraphView, NeighbourList };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The camera over the graph canvas: a pan offset and a zoom, kept as plain numbers so the whole of it is testable
|
|
3
|
+
* without a DOM.
|
|
4
|
+
*
|
|
5
|
+
* The canvas clips (`overflow: hidden`), so nothing the lens draws can leave the lens. That makes the camera the only
|
|
6
|
+
* way to reach a node outside the current view, which is why pan, wheel zoom and fit-to-view are one unit rather than
|
|
7
|
+
* three loose handlers: they all have to agree on what "the graph is at x, y, scaled by z" means.
|
|
8
|
+
*/
|
|
9
|
+
type Viewport = Readonly<{
|
|
10
|
+
height: number;
|
|
11
|
+
width: number;
|
|
12
|
+
}>;
|
|
13
|
+
type CanvasTransform = Readonly<{
|
|
14
|
+
x: number;
|
|
15
|
+
y: number;
|
|
16
|
+
zoom: number;
|
|
17
|
+
}>;
|
|
18
|
+
type ViewportPoint = Readonly<{
|
|
19
|
+
x: number;
|
|
20
|
+
y: number;
|
|
21
|
+
}>;
|
|
22
|
+
declare const minZoom = 0.25;
|
|
23
|
+
declare const maxZoom = 2.5;
|
|
24
|
+
/** One press of −/+ is a deliberate, noticeable step. A wheel notch is not: it is whatever the device reported. */
|
|
25
|
+
declare const zoomStep = 1.2;
|
|
26
|
+
/**
|
|
27
|
+
* The factor one wheel event should apply. Clamped per event so a single coarse notch — a mouse that reports whole
|
|
28
|
+
* pages — cannot cross the whole zoom range at once.
|
|
29
|
+
*/
|
|
30
|
+
declare function wheelFactor(delta: number, mode?: number): number;
|
|
31
|
+
declare const identityTransform: CanvasTransform;
|
|
32
|
+
declare function clampZoom(zoom: number): number;
|
|
33
|
+
/**
|
|
34
|
+
* Scales the graph to fit the lens and centres it. An unmeasured viewport — a panel that has not laid out yet, or a
|
|
35
|
+
* headless test — returns the identity rather than dividing by zero, so opening a node never leaves a blank canvas.
|
|
36
|
+
*/
|
|
37
|
+
declare function fitTransform(layout: Viewport, viewport: Viewport): CanvasTransform;
|
|
38
|
+
declare function panBy(transform: CanvasTransform, dx: number, dy: number): CanvasTransform;
|
|
39
|
+
/**
|
|
40
|
+
* Zooms around a point given in viewport coordinates, so whatever sits under the cursor stays under it. Without this
|
|
41
|
+
* the graph slides away as it grows and the developer chases the node they were reading.
|
|
42
|
+
*/
|
|
43
|
+
declare function zoomAround(transform: CanvasTransform, point: ViewportPoint, factor: number): CanvasTransform;
|
|
44
|
+
/** The centre of the lens, used when zoom comes from a button rather than from a cursor. */
|
|
45
|
+
declare function viewportCentre(viewport: Viewport): ViewportPoint;
|
|
46
|
+
declare function transformStyle(transform: CanvasTransform): string;
|
|
47
|
+
export { clampZoom, fitTransform, identityTransform, maxZoom, minZoom, panBy, transformStyle, viewportCentre, wheelFactor, zoomAround, zoomStep, };
|
|
48
|
+
export type { CanvasTransform, Viewport, ViewportPoint };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The panel's own icon set. Kept local so `@opetope/devtools` needs no icon package to render. */
|
|
2
|
+
import type { ReactElement } from 'react';
|
|
3
|
+
type IconProps = {
|
|
4
|
+
readonly size?: number;
|
|
5
|
+
};
|
|
6
|
+
declare function SearchIcon({ size }: IconProps): ReactElement;
|
|
7
|
+
declare function CloseIcon({ size }: IconProps): ReactElement;
|
|
8
|
+
declare function CopyIcon({ size }: IconProps): ReactElement;
|
|
9
|
+
declare function ChevronIcon({ open, size }: IconProps & {
|
|
10
|
+
readonly open: boolean;
|
|
11
|
+
}): ReactElement;
|
|
12
|
+
export { ChevronIcon, CloseIcon, CopyIcon, SearchIcon };
|