@aiquants/enrichment-react 0.6.1 → 0.8.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/README.md +271 -138
- package/dist/index.cjs +3 -3
- package/dist/index.d.cts +268 -112
- package/dist/index.d.ts +268 -112
- package/dist/index.js +3 -3
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @aiquants/enrichment-react
|
|
2
2
|
|
|
3
|
-
React UI components for storage metadata AI enrichment: the ingest console, the pipeline flow viewer, the document change history inspector and the recent changes card, the search and model-comparison workbenches, the strategy board, and the status, health, storage, and ontology cards.
|
|
3
|
+
React UI components for storage metadata AI enrichment: the ingest console, the pipeline flow viewer, the document change history inspector and the recent changes card, the search and model-comparison workbenches, the strategy board, the embedding variant board, and the status, health, storage, and ontology cards.
|
|
4
4
|
|
|
5
5
|
## Prerequisites
|
|
6
6
|
|
|
@@ -19,12 +19,15 @@ pnpm add @aiquants/enrichment-react @aiquants/enrichment-core @aiquants/console-
|
|
|
19
19
|
|
|
20
20
|
- **Host owns routes and lists.** No component names an application route or ships a domain list; the host passes `onSearch`, `onCompare`, `domainOptions`, and the like.
|
|
21
21
|
- **DOM hooks are `data-*` / `role` / element only.** Class names are styling and change freely.
|
|
22
|
-
- **Rendered inside the console root.** `InfrastructureHealthMonitor`, `PipelineFlowViewer`, `DocumentHistoryInspector`, `DocumentChangeFeed`, `ProcessingObjectBrowser`, `QdrantStorageCard`, `SyncStatusMonitor`, `DocumentIngestConsole`, the search tab's `DegradedSearchBanner`, `MetadataSearchWorkbench` and `ModelComparisonWorkbench`, and the instant slots `RecordedInstant` and `FinishedInstant` read `@aiquants/console-ui`'s `ConsoleUiRoot` and throw outside it.
|
|
22
|
+
- **Rendered inside the console root.** `InfrastructureHealthMonitor`, `EnrichmentStrategyBoard`, `PipelineFlowViewer`, `DocumentHistoryInspector`, `DocumentChangeFeed`, `ProcessingObjectBrowser`, `QdrantStorageCard`, `SyncStatusMonitor`, `OntologyStatusCard`, `DocumentIngestConsole`, `EmbeddingVariantBoard`, the search tab's `DegradedSearchBanner`, `MetadataSearchWorkbench` and `ModelComparisonWorkbench`, and the instant slots `RecordedInstant` and `FinishedInstant` read `@aiquants/console-ui`'s `ConsoleUiRoot` and throw outside it.
|
|
23
|
+
The other components do not read the root and render anywhere.
|
|
23
24
|
Every component that reads the root checks it before drawing anything (the root requirement below), so none of them reads the root, draws the root's label or writes in the root's zone under a root it was not written for.
|
|
24
|
-
This package ships no stylesheet (
|
|
25
|
-
Without it the root's `--cui-*` token variables are unset,
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
This package ships no stylesheet and paints nothing itself (no class and no inline style; `tests/migratedSources.spec.ts` checks every source), so the host loads `@aiquants/console-ui/styles/console-ui.css` once, with its `cui` cascade layer ranked above the host's reset, as `@aiquants/console-ui`'s README describes under "Loading the stylesheet" (for a Tailwind v4 host, `@import` it in the CSS entry after the layer statement `@layer theme, base, components, utilities;`).
|
|
26
|
+
Without it the root's `--cui-*` token variables are unset, every `Measured` value (a count, a byte size, an instant) loses its tabular figures and a text that moves whole wraps like plain text,
|
|
27
|
+
the cards drawn with `@aiquants/console-ui`'s parts (`SyncStatusMonitor`, `QdrantStorageCard`, `OntologyStatusCard`, `EnrichmentStrategyBoard` and `InfrastructureHealthMonitor` among them) lose their layout, type and colours, `QdrantStorageCard`'s selectable rows (`SelectableList`'s `RowButton`s) are drawn as the browser's plain buttons without the selected look,
|
|
28
|
+
the probe-state badges of `InfrastructureHealthMonitor` and `QdrantStorageCard` lose their role colours (`success-fg` / `danger-fg` and their tints), and the root's live region (`role="status"`) is drawn in the flow of the page instead of being visually hidden.
|
|
29
|
+
Nothing else is needed: the package carries no utility class, so a host needs no Tailwind source line for it and draws it with or without Tailwind
|
|
30
|
+
(`@aiquants/console-ui`'s README also says how a host without a CSS build loads the stylesheet).
|
|
28
31
|
The root must be `locale="ja"` and `formatLocale="ja-JP"`: the package's own text is Japanese and its instants are written in `ja-JP` (`RecordedInstant`), so under any other root the root's words and digit grouping would sit inside Japanese sentences beside `ja-JP` times. Every component that reads the root throws `RangeError`, naming the root's `locale` and `formatLocale`, before drawing anything under any other root.
|
|
29
32
|
|
|
30
33
|
```tsx
|
|
@@ -34,20 +37,23 @@ pnpm add @aiquants/enrichment-react @aiquants/enrichment-core @aiquants/console-
|
|
|
34
37
|
</ConsoleUiRoot>
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
- **Unmeasured is not zero.** A figure the source could not measure renders as the root's unmeasured label (`未測定` under `locale="ja"`), never as `0`. In `InfrastructureHealthMonitor`, `PipelineFlowViewer`, `DocumentHistoryInspector`, and `
|
|
40
|
+
- **Unmeasured is not zero.** A figure the source could not measure renders as the root's unmeasured label (`未測定` under `locale="ja"`), never as `0`. In `InfrastructureHealthMonitor`, `PipelineFlowViewer`, `DocumentHistoryInspector`, `QdrantStorageCard`, `SyncStatusMonitor` and `OntologyStatusCard`, every value slot that holds one is `@aiquants/console-ui`'s `Measured` (a count, or a formatted text with its `keep`), so `data-measured="false"` marks every unmeasured value slot there.
|
|
38
41
|
A run's step report in the history inspector is the one place an unmeasured figure has no slot of its own: a report field that was not measured is named in the step's `未測定:` line instead (see `DocumentHistoryInspector`), and only a page pair with one half measured keeps both `Measured` slots.
|
|
39
|
-
The other components keep their own words for a missing value: `DocumentIngestConsole` writes a missing revision as `N/A` (it reads the root for its instants and its byte format only), `MetadataSearchWorkbench` a document absent from one of the ranked lists as `ベクトルの列に無い` / `語句の列に無い` / `グラフの列に無い` and an empty column as
|
|
40
|
-
A count drawn as a value — the Qdrant totals, segment count, collection count, each collection's points and each named vector's dimensions (`次元`), the health probe's counted details, every probe latency in ms (応答時間 in both `InfrastructureHealthMonitor` and `QdrantStorageCard`), the flow metric's sample, succeeded, failed and skipped counts,
|
|
42
|
+
The other components keep their own words for a missing value: `DocumentIngestConsole` writes a missing revision as `N/A` (it reads the root for its instants and its byte format only), `MetadataSearchWorkbench` a document absent from one of the ranked lists as `ベクトルの列に無い` / `語句の列に無い` / `グラフの列に無い` and an empty column as `無し`.
|
|
43
|
+
A count drawn as a value — the Qdrant totals, segment count, collection count, each collection's points and each named vector's dimensions (`次元`), the sync monitor's tiles, the ontology card's schema and vocabulary counts, each class's count and the counted thresholds (`回`), the health probe's counted details, every probe latency in ms (応答時間 in both `InfrastructureHealthMonitor` and `QdrantStorageCard`), the flow metric's sample, succeeded, failed and skipped counts,
|
|
41
44
|
and every count the core record types as a measured count (the step duration and the report's `duration_ms` in ms, characters, pages, chunks, points, graph nodes, and the flow's p50 / p95 latencies in ms) — is `Measured`, written in the root's `formatLocale` with tabular figures and `data-measured="true"`.
|
|
42
45
|
The search workbenches write their durations inside a sentence, grouped by the root's count rule (`12,345 ms`): `MetadataSearchWorkbench`'s search time in the results card's description and `ModelComparisonWorkbench`'s execution time in each column heading; a duration that is not a non-negative safe integer throws `RangeError` naming it.
|
|
43
|
-
A measured value that is not a count is written by its own format
|
|
44
|
-
A value that reads as one unit — a byte size (`1.0 MiB`), an instant, a probe's port or boolean — is
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
A measured value that is not a count is written by its own format into the same `Measured` as a text (`text` with `keep`), in tabular figures with `data-measured="true"`, and when it was not measured the same `Measured` writes the label (`data-measured="false"`); both states carry the text's `data-cui-keep`.
|
|
47
|
+
A value that reads as one unit — a byte size (`1.0 MiB`), an instant, a probe's port or boolean, the diff's similarity (`87.5%`), the ontology card's schema version and phase and its confidence threshold — is a text that moves whole (`keep="whole"`): when it does not fit on the line it starts on, it moves to the next line whole, so a number does not part from its unit or a date from its time, and it breaks inside only in a column narrower than itself, so it never overflows the column.
|
|
48
|
+
A tile's value is the one exception: `@aiquants/console-ui`'s `MetricTile` draws its text (the Qdrant card's RAM and disk, the sync monitor's completion rate) alone on its line with `keep="wrap"`, in a tile at least as wide as the widest value it writes (`tileMinInlinePx`), so the value stays on its line.
|
|
49
|
+
A text — a probe's text, a service's version, the report's `prompt_version` and its other texts — wraps with its container (`keep="wrap"`).
|
|
50
|
+
Every recorded instant, in each of the components that write one (`InfrastructureHealthMonitor`, `PipelineFlowViewer`, `DocumentHistoryInspector`, `DocumentChangeFeed`, `SyncStatusMonitor`, `DocumentIngestConsole`, `ChildRunList`, `IngestionQueueHealthCard`, `IngestionJobBoard`), is written by the exported `RecordedInstant` (or `FinishedInstant`, for a finish time that may be open) into a `Measured` text that moves whole (`data-cui-keep="whole"`) under a `-value` hook:
|
|
51
|
+
`health-checked-at-value`, `flow-metric-last-started-at-value`, `run-history-started-at-value-${id}`, `run-started-at-value`, `run-finished-at-value`, `failed-item-time-value-${id}`, `ingest-last-run-finished-at-value-${id}`, `ingest-created-at-value-${id}`, `<testId>-started-<id>-value` / `<testId>-finished-<id>-value` (`ChildRunList`) and `ingestion-queue-measured-at-value`, `ingestion-queue-oldest-since-value` and `ingestion-queue-holder-heartbeat-value-<owner>` (`IngestionQueueHealthCard`),
|
|
52
|
+
and `<section>-created-<jobId>-value`, `<section>-created-mark-<position>-value` and `<section>-next-attempt-<jobId>-value` (`IngestionJobBoard`), so a column of instants lines up digit by digit.
|
|
53
|
+
A state shown in place of an instant is a word, not a slot: `未完了` for a run that has not finished (`FinishedInstant` writes it in `run-finished-at`, `ingest-last-run-finished-at-${id}` and `<testId>-finished-<id>`) and `記録なし` for a failure the record holds no time for (`failed-item-time-${id}`).
|
|
48
54
|
A text with nothing to read is refused like a broken count, with a `RangeError` that names the slot's hook: drawn, it would be an empty slot that claims a measurement. Pass `null` for a value that was not measured.
|
|
49
|
-
Nothing to read
|
|
50
|
-
The core record schemas still accept such a text, so the refusal is a backstop: a host refuses the value where it reads it, with the same
|
|
55
|
+
Nothing to read is `@aiquants/console-ui`'s one rule `isReadableText` (from `@aiquants/console-ui/core`): a text with no character outside Unicode's white space, control characters, format characters and default-ignorable code points. That holds the white space of both languages the records are written in, JavaScript's and Python's `\s` (U+3000 the ideographic space, U+00A0, U+FEFF, U+001C–U+001F and U+0085 among them), and the invisible characters neither counts as space (U+200B, U+00AD, U+2060, the Hangul fillers U+3164 and U+FFA0).
|
|
56
|
+
The core record schemas still accept such a text, so the refusal is a backstop: a host refuses the value where it reads it, with the same `isReadableText`.
|
|
51
57
|
So every value slot of those components, count or not, keeps one `-value` hook in both states (`data-measured="true"` or `"false"`): a test or a host that reads a value finds it whether or not it was measured. The word inside a note that names what was not measured (the report's `未測定: <fields>` line, the flow's empty-metric line and its note, each marked `data-unmeasured-note`) is the root's `labels.unmeasured`, and only there.
|
|
52
58
|
That label is the only spelling of a figure's unmeasured state: the sources write the word only in the gate-state and flag-state vocabulary (`門: 未測定`, the gate state `未測定` of the flow detail panel, `フラグ未測定`), each written directly in the element that itself carries `data-gate-state="unmeasured"` or `data-gate="unmeasured"`, which names a gate whose feature flag is absent — a state, not a figure — and stays Japanese with the rest of the copy.
|
|
53
59
|
The package's tests hold these rules over the sources and over every drawn state of every component; each test module's docstring describes the gate it keeps.
|
|
@@ -57,83 +63,104 @@ pnpm add @aiquants/enrichment-react @aiquants/enrichment-core @aiquants/console-
|
|
|
57
63
|
- **Time zone is the root's.** No component takes a `timeZone` of its own. Every component that prints a recorded instant writes it with `RecordedInstant`, which reads the root's `timeZone` (an IANA name, e.g. `Asia/Tokyo`, that `ConsoleUiRoot` validates and canonicalises) on every render and formats it in `ja-JP` to the second (`2026/09/24 01:02:03`) through `@aiquants/console-ui`'s `formatInstant`, the one instant format of every console built on that package;
|
|
58
64
|
the host resolves the zone once on the server and passes it to the root, so the server-rendered page and the hydrated one print the same string, and a host that switches the root's zone re-writes every mounted instant in the new clock.
|
|
59
65
|
A timestamp is ISO 8601 with an explicit offset (`Z`, `±hh:mm` or `±hhmm`) naming an existing date and time: every text the core record schemas refuse (`z.string().datetime({ offset: true })`) throws, and so does an accepted text that names no instant the console can show (an offset beyond ±23:59, or a wall clock outside years 1–9999 in the zone). The refusal is a `ZonedTimeError` (a `RangeError` with `reason: "malformed"`).
|
|
60
|
-
- **Layout.**
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
`
|
|
67
|
-
|
|
66
|
+
- **Layout.** Every component takes its type, spacing and colours from `@aiquants/console-ui`'s tokens and roles only (its 4 px lattice and 11 / 13 / 16 / 20 / 25 px type scale) and is drawn with its parts, with no class, inline style or colour of its own (`tests/migratedSources.spec.ts`, which checks every source of the package and exempts none).
|
|
67
|
+
Every list is the foundation's `List` / `ListItem` (a `ul` or `ol` with an explicit `role="list"`, its items spaced by a token and marked by the foundation or not); no source draws a `ul`, `ol`, `li`, `role="list"` or `role="listitem"` of its own.
|
|
68
|
+
No source measures a width with the text-width models or the 8 px grid rounding (`textWidthEm`, `monoTextWidthEm`, `ceil8`) except the history inspector's master minimum (`HISTORY_MASTER_MIN_PX`), the one host value the foundation does not define.
|
|
69
|
+
The three monitoring cards are drawn with `@aiquants/console-ui`'s parts only (`Card`, `CardSection`, `TileGrid` and `MetricTile`, `DataTable` or `SelectableList`, `DescriptionList`, `Pill`, `Notice`, `Text`), with no class, inline style or colour of their own; a tile grid's minimum tile width is measured from its tiles' words and widest values by `@aiquants/console-ui/core`'s `tileMinInlinePx` (every count with `countSampleText`, the largest seven-digit count in the root's format locale).
|
|
70
|
+
Every table column is sized by the same core (`fixedColumnPx` over what its cells hold, `FLEXIBLE_COLUMN_MIN_PX` for a flexible column): a column of recorded instants is measured with the instant `RecordedInstant` writes (`recordedInstantContent`), and a column of row IDs or revisions with the one sample of each (`identifierSamples.ts`: a 10-digit ID, a 5-digit revision; a longer one breaks inside its cell).
|
|
71
|
+
So are the strategy board (`Card`, `DataTable` with the domain as its row header and each stage's gate state as its column header's note, `Stack`, `Text`, `Switch`) and the health monitor (`Card`, one `CardSection` with one `DescriptionList` per service, `Pill`, `Text`, `Measured`, `Button`); the strategy board's stage columns are sized with `@aiquants/console-ui/core`'s `fixedColumnPx` over `CellContent` and its domain column with `FLEXIBLE_COLUMN_MIN_PX`.
|
|
72
|
+
So is the pipeline flow viewer (`Card`, `PanelTabs`, the figure system `Figure`, `FigureNode` and `FigureEdge`, a `CardSection` region holding an `h4` `CardSection` for the metrics, `DescriptionList`, `Measured`, `DataTable`, `Pill`, `Stack`, `Cluster`, `Text`, `Button`): its figure's geometry comes from `@aiquants/console-ui/core`'s figure formula and tokens (`flowGeometry`), and its gate list is the foundation's bulleted `List`.
|
|
73
|
+
So are the history inspector, its comparison view and its text diff (`Card`, `CardSection` at levels 3 and 4, `Stack`, `Cluster`, `DescriptionList`, `DataTable`, `MasterDetail`, `SelectableList`, `Pill`, `Text` with `space="preserve"` for the diff lines and JSON values, `InlineChange` for the diff's pieces); they write no `data-cui-*` attribute themselves, so every row-focus hook of their tables is the one `DataTable` draws.
|
|
74
|
+
- **One card-title scale.** Every card title (`h2`) is 16 px bold (`@aiquants/console-ui`'s `Card` title, the `title` type step) — the same scale `@aiquants/enrichment-react-router` and `@aiquants/enrichment-governance-react` use, so sibling cards on one tab read as one heading level. `tests/HeadingOutline.spec.tsx` checks every panel's `h2`.
|
|
75
|
+
- **Nothing is clipped without a way to read it, and nothing that reads as one unit breaks inside where the layout holds it.** Badges (`@aiquants/console-ui`'s `Pill`, the health badges among them) move to the next line whole instead of shrinking, and unbroken identifiers (URIs, versions, endpoints, a probe's error text, the run inspector's report texts) wrap anywhere inside their card instead of overflowing it or ending in an ellipsis.
|
|
68
76
|
A value that reads as one unit — a count with its unit, a latency, a byte size, an instant, the unmeasured label — never breaks inside in these places, so `1,234 件` never stands as `1,23` over `4 件` and `1,234 ms` never as `1,234` over `ms` there:
|
|
69
|
-
|
|
70
|
-
|
|
77
|
+
the history inspector's step report (each report value sits below its label with the column's whole width). `InfrastructureHealthMonitor`'s values and `PipelineFlowViewer`'s p50 / p95 latencies sit in `DescriptionList` rows, where a value that reads as one unit is a `Measured` text that moves whole (below).
|
|
78
|
+
Every `Measured` text that moves whole (byte sizes, instants, a probe's port or boolean, the diff's similarity, the flow metric's latencies, and the unmeasured label in those slots, `data-cui-keep="whole"`) moves to the next line whole instead of breaking, and breaks inside only in a column narrower than itself: `@aiquants/console-ui` lets every caller's text wrap, so no value overflows its column.
|
|
79
|
+
A `Measured` count (a step's duration in its summary line, the flow metric's counts) does not hold the rule itself: `Measured` joins a count and its unit with a plain space and leaves it free to wrap, so in a column narrower than the value a number can part from its unit.
|
|
71
80
|
|
|
72
81
|
## Exported Components
|
|
73
82
|
|
|
74
83
|
### 1. `DegradedSearchBanner`
|
|
75
84
|
|
|
76
85
|
The note a degraded search answer carries: a warning `Notice` under the host's `testId` whose summary (`${testId}-summary`) names the strategy asked for, the one that answered and the answer's quality (`縮退して答えています: ハイブリッド を求め、キーワード で答えました (品質: 縮退 (ベクトルもグラフも走らず))。`), or says that no tier ran when not even the lexical tier did (`… 密ベクトル を求めましたが、走った層はありません …`: the answered strategy is derived from the tiers outside SQL, so it reads `KEYWORD` then),
|
|
77
|
-
followed by one sentence per reason in the order the answer recorded them (`${testId}-reasons`,
|
|
86
|
+
followed by one sentence per reason in the order the answer recorded them (`${testId}-reasons`, the foundation's bulleted `List`, each item `${testId}-reason` with `data-reason` carrying the code and its sentence `${testId}-reason-${index}`).
|
|
78
87
|
Props: `metadata` (`is_degraded`, `requested_mode`, `search_mode`, `executed_tiers`, `quality_tier` and `degradation_reasons` of a `DocumentSearchMetadata`) and `testId`. A search that was not degraded draws nothing, so a strategy the user chose (`KEYWORD`) is not a degradation; a degraded answer without a reason draws the summary only.
|
|
79
88
|
|
|
80
89
|
Each reason is written by `degradationReasonText(code)` through `DEGRADATION_REASON_LABELS`, one sentence per code of the core's closed `SEARCH_DEGRADATION_REASONS` (a total map, so a code added to the vocabulary without wording fails to compile). A code the vocabulary does not know (a newer API) is shown as it is, never dropped, and a code that arrives twice is listed twice: a skipped tier is never hidden.
|
|
81
90
|
|
|
82
91
|
### 2. `DocumentIngestConsole`
|
|
83
92
|
|
|
84
|
-
Manual ingestion form (URI, confidentiality level, target domain, labels), file inspection result, projection capability per level (`有効` / `無効` / `設定が渡されていないため不明`, `data-state="true|false|unknown"`), and the recent-ingest table.
|
|
85
|
-
The
|
|
93
|
+
Manual ingestion form (URI, confidentiality level, target domain, labels), file inspection result, projection capability per level (`有効` / `無効` / `設定が渡されていないため不明`, `data-state="true|false|unknown"`), and the recent-ingest table, drawn on the console foundation: two `Card`s in a `Stack` (`document-ingest-console`), the form (`ingest-form-card`, the tenant and the forbidden note in its description) and the recent-ingest list (`recent-items-card`).
|
|
94
|
+
The form's groups are `Stack`s and `CardSection`s (the projection `projection-capability-card` and the labels `ingest-labels-section`, each an `h3`, the projection's two states the rows of a `DescriptionList`); the inspection result is a named region (`CardSection region`, `inspection-result-card`, the heading `リソース検証結果`) holding the status, the URI and, for a document the source holds, its name, size, type and revision as a `DescriptionList` (`inspection-result-attributes`).
|
|
95
|
+
A result the console shows (the one for the identifier in the field) is read out once through the root's announcer (`リソース検証結果: <status>`), and choosing `RESTRICTED` reads the warning (`restricted-warning-banner`, a warning `Notice`) out once the same way: each appears with its text, which a live region of its own would not read.
|
|
96
|
+
The table (`recent-items-table`, a `DataTable` named by its hidden caption `最近の取り込み履歴`, rows that hold controls) shares one row test id, `recent-item-row`, with `data-item-id` on a readable row; an empty list draws the table's empty text (`recent-items-table-empty`) and no table. A sync status is a `Pill` in `syncStatusTone`'s tone (a status the build does not know is its stored word in a neutral pill), and a run's status a `Pill` in `PIPELINE_RUN_STATUS_TONES`' tone.
|
|
97
|
+
The table shows sync status, the last pipeline run (`ingest-last-run-${id}`: the run status pill `ingest-last-run-status-${id}` with `data-status` and `data-presentation`, the failed step, and the finish time `ingest-last-run-finished-at-${id}`, its instant under `ingest-last-run-finished-at-value-${id}` or `未完了` while the run is open), the registration time (`ingest-created-at-value-${id}` inside `ingest-created-at-${id}`), both in the root's zone, and — when `onShowHistory` is given —
|
|
86
98
|
a `履歴` button per row (`ingest-history-button-${id}`) that asks the host to open the document's whole processing history (called for rows without a recorded run too; the inspector then says there is none).
|
|
87
99
|
|
|
88
|
-
Required props: `tenantId`, `sources: readonly SourceSpec[]` and `domainOptions: readonly { key; label }[]` (empty →
|
|
89
|
-
`sources` is the deployment's source catalog (`GET /ai/sources`, `@aiquants/enrichment-core`'s `SourceCatalogSchema`), and the console names no scheme of its own: one
|
|
90
|
-
the
|
|
100
|
+
Required props: `tenantId`, `sources: readonly SourceSpec[]` and `domainOptions: readonly { key; label }[]` (empty → no domain select, `ingest-domain-empty` explains why, and the submit button says it cannot ingest). The console reads the root's zone and format, so it renders inside `ConsoleUiRoot`.
|
|
101
|
+
`sources` is the deployment's source catalog (`GET /ai/sources`, `@aiquants/enrichment-core`'s `SourceCatalogSchema`), and the console names no scheme of its own: with two or more sources one `SegmentedControl` option per source in the catalog's order (`scheme-select-${scheme}`, labelled `<scheme>:// (<title.ja>)`; the first source is selected at first, and again when the selected one leaves the catalog), with one source that source written as text (`scheme-single`),
|
|
102
|
+
the input (`ingest-file-uri-input`, a console `TextInput` labelled `ファイル URI / リソース識別子 (必須)`) with the source's prefix drawn in its frame (`ingest-file-uri-input-prefix`) and the source's guidance (`guidance.ja`) as its description, the check of the identifier written after the prefix (`identifierMatches`: a full match of the source's form, refused as `<title>の識別子の形ではありません (例: <input_example>)。`).
|
|
91
103
|
The console reads a typed or pasted text in this order: a full URI of a catalog source is split by its longest prefix (`sourceOfUri`) and kept as written, a text that fully matches the chosen source's form is that source's identifier, and only then is an identifier extracted from a pasted text such as a share link (`pastedIdentifier`), so a path that merely contains a share link's marks stays with its own source.
|
|
92
|
-
A URI no source's prefix starts names every prefix of the catalog in its refusal, and `..` stays refused as the console's own guard. An empty catalog shows `取り込める源が配備にありません。` (`ingest-no-sources`, a warning `Notice`) with no
|
|
93
|
-
`recentItems` is `readonly RecentIngestEntry[]`: a readable row `{ kind: "readable", item: RecentIngestItem }` or a restricted mark (`RestrictedIngestMark`, V11) that the table draws as one row with the fixed text `隔離 (RESTRICTED) の文書`, its registration time and its sync status only (`recent-item-
|
|
94
|
-
a mark whose `syncStatus` is `null` (the document has no enrichment record) reads the neutral `取り込みの記録なし` (`NO_ENRICHMENT_RECORD_TEXT`, no status badge) in its status cell (`recent-item-restricted-status-${position}`). Each `RecentIngestItem` carries `lastRun: ReadableEntry<{ runId; status; retryScheduled; failedStepKey; finishedAt }> | RestrictedRunMark | null`;
|
|
104
|
+
A URI no source's prefix starts names every prefix of the catalog in its refusal, and `..` stays refused as the console's own guard. A refusal is the input's field error (`ingest-file-uri-input-error`, with `aria-invalid`). An empty catalog shows `取り込める源が配備にありません。` (`ingest-no-sources`, a warning `Notice`) with no scheme choice, no input and no inspection, and the submit button says so (`SUBMIT_BLOCKED_REASONS.noSource`).
|
|
105
|
+
`recentItems` is `readonly RecentIngestEntry[]`: a readable row `{ kind: "readable", item: RecentIngestItem }` or a restricted mark (`RestrictedIngestMark`, V11) that the table draws as one row with the fixed text `隔離 (RESTRICTED) の文書`, its registration time and its sync status only (a `recent-item-row` with `data-restricted-mark="ingest"` and `data-position`, its position in the response), with no history entry and no row action;
|
|
106
|
+
a mark whose `syncStatus` is `null` (the document has no enrichment record) reads the neutral `取り込みの記録なし` (`NO_ENRICHMENT_RECORD_TEXT`, no status badge) in its status cell (`recent-item-restricted-status-${position}`). Each `RecentIngestItem` carries `lastRun: ReadableEntry<{ runId; status; retryScheduled; failedStepKey; finishedAt }> | RestrictedRunMark | null`;
|
|
107
|
+
a `FAILED` run with `retryScheduled: true` reads `失敗 (再試行予定)`, a `RUNNING` run with `retryScheduled: true` (waiting for its next attempt) reads `再試行待ち`, and a last run that is a mark reads `隔離されていた期間の実行` with its status (`ingest-last-run-restricted-${id}`).
|
|
95
108
|
Each `RecentIngestItem` also carries `unlock: { protectionScheme; disposition; copyReason } | null`, how the revision lost its password (`null` for a revision without one): the row writes the protection removed and how the
|
|
96
109
|
unlocked file became the original, with the copy's reason (`ingest-unlock-${id}`, `data-disposition`, `data-protection`, the names being `@aiquants/enrichment-core`'s labels). When `onPromoteUnlockedCopy` is given, a copy that is the original because the
|
|
97
110
|
operator could not write the source (`isPromotable`) offers `源を写しで上書き` (`ingest-promote-button-${id}`) on its current revision; a copy of a source never overwritten (a mail, another application's object, a host file) offers nothing.
|
|
98
|
-
`isBusy` says that one of the console's submissions (an inspection, an ingestion, a retry or a promotion) awaits its answer: the host carries them on one fetcher, which a second submission would replace, so every submit control is disabled
|
|
99
|
-
|
|
100
|
-
|
|
111
|
+
`isBusy` says that one of the console's submissions (an inspection, an ingestion, a retry or a promotion) awaits its answer: the host carries them on one fetcher, which a second submission would replace, so every submit control is unavailable meanwhile (`aria-disabled`, the press swallowed, focus kept): the inspection and the submit button draw their reason beside them (`INSPECT_BLOCKED_REASONS`, `SUBMIT_BLOCKED_REASONS`), and the row actions, whose cells hold no wrapping reason, are described by one note above the table (`recent-items-busy-note`, `ROWS_BUSY_TEXT`).
|
|
112
|
+
`canSendCommands` (required) says whether the operator may send the console's commands at all: when it is `false`, the inspection, the submit and every row's 再試行, 再同期 and 昇格 are unavailable from the first render, described only by one note under the heading (`ingest-forbidden-note`, `INGEST_FORBIDDEN_SENTENCE`) instead of any passing reason (no busy note, no blocked reason), and a submit through the form sends nothing; the scheme choice, the identifier, the classification and domain choices, the labels and the advanced settings keep working.
|
|
113
|
+
Each readable row's status cell carries the row's sync status as `data-sync-status` (`ingest-item-status-${id}`) besides its badge.
|
|
114
|
+
The row `promotingId` names is the only busy one and reads `源を写しで上書き中...` (`aria-busy`); every row action's accessible name names its row's file around the short visible verb (`<file name> の源を写しで上書き`, `<file name> を再試行`, `<file name> の履歴`). A promotion requested from the console (a click, not a focus) is announced once the row records it (`copy_promoted`),
|
|
115
|
+
and focus moves to the row's unlock record only when it fell to the document body, never from another control.
|
|
116
|
+
Every control of the console is a console-ui primitive and so a 44 × 44 px target: the label tags (`preset-tag-${tag}`, `ingest-add-label-button`, `remove-label-${key}` named `ラベル <key> を削除`), the label fields (`ingest-label-key-input`, whose Enter adds the label without sending the form; `ingest-label-category-select`; the `Slider` `ingest-label-confidence-slider`),
|
|
117
|
+
the advanced settings' console `Disclosure` `メタデータ詳細オーバーライド (高度な設定)` (`ingest-toggle-advanced`: its button `ingest-toggle-advanced-button` with `aria-expanded` and `aria-controls` over its panel `ingest-toggle-advanced-panel`, hidden while closed), whose `TextInput`s (`advanced-settings-panel`; `ingest-department-input` takes `inputMode="numeric"`; its value stays the typed text) are drawn only while it is open, and the submit button, busy as `取り込み投入中...` while it sends.
|
|
118
|
+
No text of the console is cut or moves: a file name and URI wrap in their column, and an identifier is drawn in the mono face on a line of its own, never mixed into a line of the sans face.
|
|
101
119
|
A run that failed at `unlock_document` adds the hint that a file password matching the file must be added before a retry, and that a protection no component removes must be removed where the file was made (`ingest-unlock-hint-${id}`).
|
|
102
120
|
`listRefusal?: string` is the full sentence that refuses the operator the list (no `file:read`, or a column-scoped one; V6 / V9): when set, the list is replaced by that sentence in a warning `Notice` (`recent-items-refused`), with no row, no empty sentence (`取り込み履歴はありません。` would claim that nothing was ingested) and no row action.
|
|
103
121
|
`onShowAllObjects?: () => void` draws `全ての処理オブジェクトを見る` (`recent-items-all-objects`, `@aiquants/console-ui`'s `Link`, an in-document move) beside the list's heading and calls it when pressed; it is not drawn while the list is refused (the full list refuses the same operator). The table here is a window of the recent rows; the full list is `ProcessingObjectBrowser`.
|
|
104
122
|
The status badges read their words from `SYNC_STATUS_LABELS` (`syncStatusPresentation.ts`), the table `ProcessingObjectBrowser` reads too.
|
|
105
|
-
The inspection result writes the file's size under `inspection-file-size-value` (`
|
|
123
|
+
The inspection result writes the file's size under `inspection-file-size-value` (`Measured` with `keep="whole"`: tabular figures, `data-measured="true"`, `data-cui-keep="whole"`, moved to the next line whole instead of breaking between the number and its unit) with the package's one byte format, the one `QdrantStorageCard`'s RAM and disk tiles use:
|
|
106
124
|
binary units (B below 1 KiB, then KiB / MiB / GiB / TiB) chosen after rounding (1,048,575 bytes is `1.0 MiB`), the mantissa grouped in the root's format locale (`3,072.00 TiB`). `fileSize` must be a non-negative safe integer; otherwise the console throws `RangeError` naming `fileSize`, with `@aiquants/console-ui`'s `formatCount` refusal as its `cause`, instead of writing a negative size as `0 B` or a fraction without a unit.
|
|
107
125
|
That refusal is a backstop: thrown while the console renders, it would take the operator's unsent form input with the console. The host refuses a broken size where it reads the inspect response (its schema fails the inspect request, shown as a failed inspection), so a value that reaches the console is valid.
|
|
108
126
|
A missing revision is written `N/A`, not the root's unmeasured label.
|
|
127
|
+
The inspection of a document that does not exist carries its URI alone (`{ fileUri, exists: false }`): the card reads `リソース不達` with the URI and draws no name, size, type or revision.
|
|
109
128
|
|
|
110
129
|
### 3. `EnrichmentStrategyBoard`
|
|
111
130
|
|
|
112
|
-
Per-domain strategy rows with each stage's enablement laid over the tenant feature flags.
|
|
131
|
+
Per-domain strategy rows with each stage's enablement laid over the tenant feature flags, as a `Card` (`enrichment-strategy-board`) whose description holds the board's notes (`strategy-inert-note` and the switches' reason, if any) above a domain × stage `DataTable` (`strategy-grid`; its caption `ドメイン別の段の開閉` is visually hidden and differs from the card's title, since the card and the table's scroll frame are both named regions) of `row-control` rows (`strategy-row`, with `data-domain-key`, `data-strategy-name`, `data-provenance` and `data-active`).
|
|
132
|
+
The domain column is the row header (`th scope="row"`): its content is the domain's label (`strategy-domain-${domainKey}`, bold like every header cell), and under it the row header's note (`rowHeaderNote`, `strategy-domain-note-${domainKey}`, in the regular weight) holds the domain's key and the strategy's name, the row's provenance (`strategy-provenance-${domainKey}`) and, for an inactive strategy, `strategy-inactive-${domainKey}`; the note is part of the row header's name, so every cell of the row is read with it.
|
|
133
|
+
Each stage column's header carries its tenant gate's state as the header's note, so it is read with every cell of the column: a word in its text role (`strategy-gate-${stage}` with `data-gate`: `フラグ有効` in `success-fg`, `フラグ無効` in `warning-fg`, `フラグ未測定` and `テナントの門なし` in `fg-muted`, each on the header's `subtle`), and `今は効かない` (`strategy-inert-${stage}`) for a column no step reads. The stage columns share one fixed width,
|
|
134
|
+
the widest of the stage columns measured from the stage header with every word its note may show and the hidden-name switch of its cells (`fixedColumnPx` with `note` and the `switch` content); the domain column takes the rest. With no strategy the table is replaced by its empty sentence (`strategy-grid-empty`).
|
|
135
|
+
Each stage is a console `Switch` in its cell (`strategy-stage-${domainKey}-${stage}`), named `<domain label> の <stage label>` (followed by `(今は効かない)` for a column no step reads) with the label visually hidden, since the headers show both, and carrying `data-enabled`, `data-effective` and `data-inert`; an enabled stage whose gate is unmeasured or closed says so under its switch (`strategy-unmeasured-${domainKey}-${stage}`, `strategy-blocked-${domainKey}-${stage}`).
|
|
136
|
+
`onToggleStage` and `canSendCommands` are required. When the operator may not send commands (`canSendCommands` false) and while a change is sent (`isUpdating`) the switches keep focus but change nothing, each described by one note above the table (`strategy-forbidden-note` / `strategy-updating-note`, `STRATEGY_FORBIDDEN_TEXT` / `STRATEGY_UPDATING_TEXT`; the permission is the lasting reason, so its note replaces the updating one, and neither is drawn while the board draws no switch). The board reads the root, so it renders inside `ConsoleUiRoot`.
|
|
113
137
|
|
|
114
138
|
### 4. `InfrastructureHealthMonitor`
|
|
115
139
|
|
|
116
|
-
Connectivity probes for Qdrant, Neo4j, Redis, SQL Server, and the backend API. Required prop: `health` (the check time is written in the root's zone under `health-checked-at-value`, inside `health-checked-at
|
|
117
|
-
Each service
|
|
140
|
+
Connectivity probes for Qdrant, Neo4j, Redis, SQL Server, and the backend API, as a `Card` (`infrastructure-health-monitor`, `data-healthy`). Required prop: `health` (the check time is written in the root's zone under `health-checked-at-value`, inside `health-checked-at`, in the card's description); optional `onRefresh` draws the re-check `Button` (`health-refresh-button`, `再確認`, busy as `再確認中…` while `isRefreshing`) in the card's actions beside the overall badge. The card takes no class (`className` is not a prop).
|
|
141
|
+
Each service is one `CardSection` (`service-card-${service}`, an `h3` titled with the service's name and subtitled with its role) holding one `DescriptionList` (`service-details-${service}`, a row `service-details-${service}-<item>` per value): `状態` (`status`), `バージョン` (`version`), `応答時間` (`latency`), every detail the probe emitted (`detail-<key>`), `接続先` (`endpoint`, `service-endpoint-${service}` in the mono face) and `エラー内容` (`error`). The details differ per service,
|
|
142
|
+
so each service reads as its own section under its own heading rather than as a table row whose cells would hold lists. Text details, the versions the probes read, the endpoint and the error text wrap anywhere inside the value column; the endpoint and the error text are shown in full.
|
|
143
|
+
Each service's version sits under one hook, `service-version-value-${service}` in the `version` row: the version the probe read (`data-measured="true"`), set in the mono face, or the root's unmeasured label when it read none (`version: null`, `data-measured="false"`), never `N/A`, set like the unmeasured label of every detail row and not in the mono face. A version with nothing to read (`isReadableText`, see **Unmeasured is not zero**) throws `RangeError` naming the hook.
|
|
118
144
|
A host whose probe cannot read a version passes `null`: a stand-in word such as `unknown` would be drawn as a version the probe read.
|
|
119
145
|
Each probe's state reads one word of the console's probe vocabulary (`service-status-${service}`): `正常接続` or `接続エラー`, the same words the overall badge composes (`全サービス正常接続` / `一部サービス接続エラー`) and `QdrantStorageCard` writes for the same probe.
|
|
120
|
-
Every probe-state
|
|
121
|
-
Each probe's error text (`service-error-${service}`) is a failure mark in either state, always `danger-fg` on
|
|
122
|
-
|
|
123
|
-
The
|
|
124
|
-
The error text is drawn only when the probe's `errorMessage` has something to read (the value slots' definition above); it breaks anywhere and is shown in full, like the endpoint. An `errorMessage` with nothing to read (empty or only white space, as an `Error` thrown with no message gives) says nothing, so the card draws no box for it and the failure reads from the probe's badge; the card does not throw on it, since a monitor that stopped on a probe's empty error would hide the failure it exists to show.
|
|
146
|
+
Every probe-state badge — the overall badge and each probe's badge — is a tinted `Pill` of the state's tone (`PROBE_STATE_TONES`): `success-fg` on `success-tint` for a connected probe and `danger-fg` on `danger-tint` for a failed one (both in `@aiquants/console-ui`'s `CONTRAST_PAIRS`), the badge `QdrantStorageCard` draws for the same probe. The word alone carries the state: a badge holds no icon or dot, and nothing in it pulses or fades.
|
|
147
|
+
Each probe's error text (`service-error-${service}`) is a failure mark in either state, always `danger-fg` on the card's surface, a connected probe's included (the foundation has no tinted text block outside `AlertMessage`, whose `role="alert"` would announce every failed probe at each render).
|
|
148
|
+
Outside those marks nothing in a service's section changes with the probe's state (no element or attribute); the state is shown by the marks alone.
|
|
149
|
+
The error text is drawn only when the probe's `errorMessage` has something to read (`isReadableText`, the value slots' definition above); it breaks anywhere and is shown in full, like the endpoint. An `errorMessage` with nothing to read (empty, as an `Error` thrown with no message gives, or only white space or invisible characters) says nothing, so the card draws no row for it and the failure reads from the probe's badge; the card does not throw on it, since a monitor that stopped on a probe's empty error would hide the failure it exists to show.
|
|
125
150
|
Each probe's latency is a `Measured` count with `ms` (`health-latency-value-${service}`, `data-measured="true"`), grouped like every other duration of the console (`1,234 ms`), and must be a non-negative safe integer (otherwise `RangeError`). Measure it on a monotonic clock and round to whole ms (`Math.round(performance.now() - started)`): the wall clock (`Date.now()`) can step back during a probe, and a negative latency stops the card.
|
|
126
151
|
Every detail's value sits under one hook, `health-detail-value-${service}-${key}`. A counted detail (`collectionsCount`, `nodeCount`, `totalKeys`, `endpointsCount`) given as a number is a `Measured` count with `件` and must be a non-negative safe integer (otherwise `RangeError`); any other number (a port, not grouped) and a boolean (`true` / `false` read 有効 / 無効) are figures, and a number that is not finite (`NaN`, `Infinity`) throws `RangeError` naming its hook; a string, a counted detail given as a string included, is the probe's text (all `data-measured="true"`).
|
|
127
|
-
A text detail must
|
|
128
|
-
Each row lays out
|
|
152
|
+
A text detail must have something to read (`isReadableText`): it throws `RangeError` naming its hook (`health-detail-value-sqlServer-fullVersion: …`), and the whole card with it, so a host passes `null` for a text it could not read, never a stand-in word (`unknown`) or an empty string, which would read as a value the probe read. A detail the probe could not read (`null`), counted or not, is the root's unmeasured label (`data-measured="false"`), so a Redis whose DBSIZE failed (`totalKeys: null`) reads as unmeasured, never `0 件`.
|
|
153
|
+
Each row lays out as every `DescriptionList` row does (the term a third of the width, the value the rest): a port, a boolean's word and the unmeasured label are `Measured` texts that move whole (`data-cui-keep="whole"`), so they move to the next line instead of breaking inside; a text (an edition, a URI) and a version the probe read wrap with the value column (`data-cui-keep="wrap"`). A detail key that is also the name of a fixed row (`version`, `status`) is a row of its own (`detail-version`), never a replacement.
|
|
129
154
|
|
|
130
155
|
### 5. `MetadataSearchWorkbench`
|
|
131
156
|
|
|
132
|
-
The document search of the search tab (the public API's `GET /ai/search/documents`). Props: `tenantId`, `onSearch(params, signal)` and the optional `initialParams: Partial<DocumentSearchParams>`, which fills the query, the strategy, its controls and the filters.
|
|
157
|
+
The document search of the search tab (the public API's `GET /ai/search/documents`). Props: `tenantId`, `variantSets: readonly VariantSetView[]` (the tenant's variant sets, `VariantSetList.variant_sets`), `onSearch(params, signal)` and the optional `initialParams: Partial<DocumentSearchParams>`, which fills the query, the strategy, its controls, the variant set and the filters.
|
|
133
158
|
The host owns the route and the transport: `onSearch` answers a `DocumentSearchAnswer`, or rejects with an `Error` whose message the workbench shows as it is (`search-failure`, announced again when the same failure repeats and removed by the next answer), and it should stop when `signal` aborts.
|
|
134
159
|
|
|
135
160
|
- **Query card** (`search-query-card`, with `data-mode` and `data-running`): the query (`search-query`), the strategy (`search-mode`, a segmented control of the core's `SEARCH_MODES` worded by `SEARCH_MODE_LABELS`, `HYBRID` unless `initialParams` names another, its description in `search-mode-description`) and the page size (`search-limit`).
|
|
136
161
|
Only the controls the chosen strategy reads are drawn and sent: the similarity floor for `DENSE` (`search-threshold`, starting at the midpoint of its bounds, since no floor is the request's own default), the fusion (`search-fusion`, `rrf` or `linear` by `SEARCH_FUSION_LABELS`) and its `k` (`search-rrf-k`) for `HYBRID`, and the vector weight `α` (`search-alpha`) for a linear fusion. The numeric bounds and defaults come from `SEARCH_PARAM_LIMITS`.
|
|
162
|
+
For a strategy with a vector tier (`SPARSE`, `DENSE`, `HYBRID`; `MODE_TIERS`), the embedding variant set the vector tiers read (`search-variant`): `本番 (default)` first, then the active sets in key order, each named with its projected share (`variantSetOptionLabel`: `eg2-v1 (投影済み 70.0%)`, or `(対象の文書なし)` for a set whose rows count no document). Production is the API's default and is never sent; any other set travels as `variant_key`. A chosen set that leaves the active list reads as production again.
|
|
163
|
+
A set that holds less than every document says so in a note (`search-variant-partial`: `組 <key> はテナントの文書の 70.0% (7 / 10 件) を投影しています。…`), since a document it lacks cannot be found through its vector tiers. The run trace names the set (`変種の組: …`), and so does the results card's description (`· 組: …`).
|
|
137
164
|
- **Filters** behind the `詳しい条件` disclosure (`search-filters`): the confidentiality level (`search-confidentiality`, `全ての区分` or one of the core's `FILTERABLE_CONFIDENTIALITY_LEVELS` worded by `CONFIDENTIALITY_LEVEL_LABELS`, so `RESTRICTED` is never offered), the department id (`search-department`), the MIME type (`search-mime-type`, a contains match) and the tag (`search-tag`, an exact match). A filter is sent only when given, trimmed.
|
|
138
165
|
- **Developer presets** behind the `開発者のプリセット` disclosure (`search-presets`): each of `SEARCH_PRESETS` (`search-preset-${id}-apply`) sets the strategy and the page size, and the floor or the fusion only where the preset names them; applying one is announced.
|
|
139
166
|
- **Checked before sending:** the query (1–500 characters after trimming), the department (empty, or a positive integer) and the text filters (at most 100 characters after trimming). Nothing is sent while a field is unacceptable, and each field says why only once a send has been attempted.
|
|
@@ -154,126 +181,168 @@ A fused value outside [0, 1] (the meter's range) or a search time that is not a
|
|
|
154
181
|
|
|
155
182
|
### 6. `ModelComparisonWorkbench`
|
|
156
183
|
|
|
157
|
-
Runs one query against
|
|
158
|
-
|
|
184
|
+
Runs one query against 2 to 4 embedding variant sets (the public API's `POST /ai/search/compare-variants`) and lays their ranked chunks side by side in a comparison grid (`compare-grid`: a column per set in the request's order, the rows `密の層` and `疎の層` saying which tiers ran (`走った` / `走らなかった` / `この戦略では走らせない`), then a row per rank, each cell `compare-${position}-${rank}-${layout}`).
|
|
185
|
+
Props: `variantSets: readonly VariantSetView[]` (the tenant's variant sets) and `onCompare(request, signal)`, which answers the core's `VariantComparisonAnswer` or rejects with an `Error` whose message the workbench shows, and should stop when `signal` aborts.
|
|
159
186
|
|
|
160
|
-
- **Form** (`compare-card`, with `data-running`; `compare-form`):
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
- **
|
|
164
|
-
- **
|
|
187
|
+
- **Form** (`compare-card`, with `data-running`; `compare-form`): the sets to compare (`compare-sets`, a `CheckboxGroup` of `本番 (default)` and the active sets with their projected shares, the first two chosen by default; a set that leaves the list leaves the choice), the strategy (`compare-mode`, `DENSE` or `HYBRID` by `COMPARISON_MODE_LABELS`; `HYBRID`, the API's default (`API_DEFAULT_COMPARISON_MODE`), is pressed first and never sent, so only a `DENSE` comparison names its mode), the domain (`compare-domain`, `全てのドメイン` or a text domain every chosen set other than production has a row of;
|
|
188
|
+
sent only when chosen), the query (`compare-query`, 1–500 characters after trimming) and the page size (`compare-limit`).
|
|
189
|
+
The request is built with the core's `VariantComparisonRequestSchema`. Fewer than `SEARCH_PARAM_LIMITS.comparisonVariants.min` (2) or more than its `max` (4) sets, or an empty query, say why at their fields once a send has been attempted and send nothing; fewer than two offered sets draw an info note (`compare-variants-insufficient`) instead of the form.
|
|
190
|
+
- **Cells:** a chunk is identified by `<metadata_id>:<chunk_index>` (`variantHitKey`). A cell shows `文書 <id> · チャンク <index> · <score>`, the score rounded to `SCORE_DISPLAY_DECIMALS` (4) decimals, and the chunk's text; past the end of a set's list it says `この順位の結果無し`. A column heading reads `<set> · <execution ms> ms` (production as `本番 (default)`). Scores are never normalized: two sets may embed with different models, so a cosine from one is not comparable to a cosine from another.
|
|
191
|
+
- **Agreement** with the first set (`compare-agreement`, a row per other set, `compare-agreement-row` with `data-variant-key`) is measured on ranks by `rankAgreement(a, b)`: the shared chunks with both counts (`compare-shared-count-${position}`), the Jaccard overlap (`compare-overlap-ratio-${position}`) and the mean absolute rank difference of the shared chunks (`compare-rank-difference-${position}`); `compare-summary` names the strategy that ran, the baseline and whether the bodies are shown (`compare-text-hidden`).
|
|
192
|
+
- **What did not run or is hidden:** a set whose strategy's tier did not run (`dense_ran` / `sparse_ran` false; a `DENSE` comparison runs no sparse tier and says nothing of it) says which and why in a warning of its own (`compare-degraded-${position}`, its reasons by `degradationReasonText`); a body the caller's scope hides (`text_hidden`, or a `null` text) reads `非表示 (列のスコープ)` in every cell; when no set has a chunk, `compare-empty` says so.
|
|
193
|
+
- **Failures:** an answer without one of the requested sets (`答えに組 <set> の結果がありません。`) or an `onCompare` that rejects (its `Error`'s message, or `比べを実行できませんでした。` for a rejection that is not an `Error`) shows the `compare-failure` alert and removes the previous results; a rejection never escapes the component. One comparison runs at a time, and one the workbench leaves behind is aborted through its signal.
|
|
165
194
|
|
|
166
|
-
An execution time that is not a non-negative safe integer throws `RangeError` naming the
|
|
195
|
+
An execution time that is not a non-negative safe integer throws `RangeError` naming the set instead of being drawn.
|
|
167
196
|
|
|
168
197
|
```tsx
|
|
169
|
-
<ModelComparisonWorkbench
|
|
170
|
-
variants={[{ strategyId: 11, label: "GENERAL_DOCUMENT / default", embeddingModel: "text-embedding-3-large" }, { strategyId: 12, label: "GENERAL_DOCUMENT / ruri", embeddingModel: "cl-nagoya/ruri-v3-310m" }]}
|
|
171
|
-
onCompare={(input, signal) => api.compareVariants(input, signal)}
|
|
172
|
-
/>
|
|
198
|
+
<ModelComparisonWorkbench variantSets={variantSetList.variant_sets} onCompare={(request, signal) => api.compareVariants(request, signal)} />
|
|
173
199
|
```
|
|
174
200
|
|
|
175
201
|
### 7. `OntologyStatusCard`
|
|
176
202
|
|
|
177
|
-
Ontology schema version, lifecycle phase, promotion thresholds, and class / predicate counts.
|
|
178
|
-
The
|
|
203
|
+
Ontology schema version, lifecycle phase, promotion thresholds, and class / predicate counts, as a `@aiquants/console-ui` `Card` (`ontology-status-card`, a region named by its `h2`). The card reads the root, so it renders inside `ConsoleUiRoot` and refuses any root but the Japanese console.
|
|
204
|
+
The evolution badge (`ontology-evolution-badge`) is a tinted `Pill` in the card's actions: `success` 自律的オントロジー進化: 有効, or `neutral` 自律進化: 停止中.
|
|
205
|
+
A `DescriptionList` (`ontology-summary-<key>` rows) holds the schema's identity from the migration ledger (`ontology-schema-version`, mono), the tenant namespace (`ontology-tenant-id`, mono) and the promotion thresholds (`ontology-thresholds-info`: the confidence as given, `0.85`, and the co-occurrence count with `回`).
|
|
206
|
+
An unwritten ledger reads `<schema> (移行台帳は未記録)`, never a version and a phase; once it is written, a version or a phase it does not hold is the root's unmeasured label in its own slot (`ontology-schema-version-number`, `ontology-schema-phase`).
|
|
207
|
+
A `TileGrid` of four `MetricTile` counts with `件` follows: the unique constraints (`ontology-constraints-count`), the indexes the schema DDL creates (`ontology-indexes-count`), every index `SHOW INDEXES` returns (`ontology-indexes-total`) and the HITL promotion candidates (`ontology-unresolved-count`, `warning` while positive), each value under `<tile>-value`.
|
|
208
|
+
Three `CardSection`s close the card: the class vocabulary (`ontology-classes`) and the predicate vocabulary (`ontology-predicates`), each counted in its subtitle with `種` (`ontology-classes-count`, `ontology-predicates-count`) and listed as a `DescriptionList` of name and description
|
|
209
|
+
(`ontology-class-${name}`, a class's count in a neutral `Pill`; `ontology-predicate-${name}`, the direction on a mono line), or said to be unsupplied (`ontology-classes-unsupplied`, `ontology-predicates-unsupplied`);
|
|
210
|
+
and the evolution policy (`ontology-evolution-policy-card`), a `DescriptionList` of its three rules with the gate's thresholds.
|
|
211
|
+
Every value the source returned as `null` — a count, a threshold, a vocabulary's count — is the root's unmeasured label (`data-measured="false"`), never `0` or a default, so a supplied empty vocabulary (`0 種`) and an unsupplied one read apart. Every count is grouped in the root's format locale and must be a non-negative safe integer (`Measured` throws `formatCount`'s `RangeError` otherwise); two classes or two predicates with one name throw `DescriptionList`'s `RangeError`.
|
|
179
212
|
|
|
180
213
|
### 8. `PipelineFlowViewer`
|
|
181
214
|
|
|
182
|
-
The ingestion pipeline as the flow each document kind actually takes, drawn from `
|
|
183
|
-
|
|
184
|
-
- **Kind
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
215
|
+
The ingestion pipeline as the flow each document kind actually takes, drawn from the flow catalogue the API answers (`GET /ai/pipeline/flows`, `@aiquants/enrichment-core`'s `PipelineFlowCatalog`, required as `catalog`). The flows, their nodes and gates and the MIME rules live only in the Python core's flow catalogue and step registry; the viewer keeps no table of its own. What the answer does not carry is not drawn: a step's description and capability belong to the step catalogue (`GET /ai/pipeline/steps`), and no node has an endpoint, since the executor runs every step in process.
|
|
216
|
+
|
|
217
|
+
- **Kind tabs**: the console's `PanelTabs` (`flow-kind`, tabs `flow-kind-${kind}`, panel `flow-kind-panel` named by the selected kind), one tab per document kind a document flow of the catalogue names (`documentKindFlows(catalog)`), in the document-kind vocabulary's order (not the catalogue's), labelled by the core's `DOCUMENT_KIND_LABELS`, the one name the console gives a kind (a flow may serve several kinds, so its own title cannot tell their tabs apart).
|
|
218
|
+
The ARIA tabs pattern: only the selected tab is in the Tab order; ArrowLeft / ArrowRight (wrapping), Home, and End move focus and select; a press on the selected kind changes nothing; every tab is a 44 px target.
|
|
219
|
+
A kind's tab draws the flow whose `document_kinds` name it (the flow a document of that kind runs when no binding rule of the deployment picks another), named beside the figure as `流れ: <title> (<key> 版 <version>)` (`flow-identity`). A catalogue whose document flows name no kind draws no tab and no figure (`flow-no-document-flows`), only the MIME table.
|
|
220
|
+
- **Figure** (`flow-svg`): `@aiquants/console-ui`'s figure system, a `group` `Figure` (`role="group"` named `<kind> のパイプライン`, so its node buttons stay in the accessibility tree) drawn at its natural size in the tab panel, inside the figure's horizontal scroll frame (`flow-svg-frame`, which takes no Tab stop) above the visible hint `flow-scroll-hint`, which describes the panel.
|
|
221
|
+
The figure is never scaled to fit (its CSS box is in rem, so it grows with the root font size as a picture). Nodes `flow-node-${id}` are the flow's nodes in the executor's order, each a `FigureNode` box and a disclosure button for the detail panel (`role="button"` with `aria-expanded`, and `aria-controls` naming the panel while it is open): click, Enter, or Space opens the panel on the node, or closes it when it already describes that node; Escape pressed on a node clears the selection (the figure's `onEscape`).
|
|
222
|
+
A node carries `data-node-id`, `data-step-key` (its step), `data-gate-state` = `open` / `closed-skip` / `closed-fail` / `unmeasured` and `data-gate-policy` = `none` / `skip` / `fail` / `mixed`, and the foundation's `data-selected`, `data-cui-tone` and `data-cui-outline`: its tone and outline follow the gate state (`GATE_STATE_PRESENTATION`: open `success`, closed and skipping `warning`, closed and failing `danger`, unmeasured a `dashed` `neutral`), and a selected node is drawn on `info-tint` with a line twice as wide.
|
|
223
|
+
It draws its title (`title.ja`, `flow-node-${id}-name`), its phase (`flow-node-${id}-note-0`) and its gate line (`flow-node-${id}-note-1`) in `fg`, on its plate (`flow-node-${id}-plate`), and the foundation's focus ring (`flow-node-${id}-ring`, `focus`, 2 px wide and 2 px outside the node, shown only on `:focus-visible`).
|
|
224
|
+
- **Edges: the order, drawn as the transitive reduction.** A node's `depends_on` holds every edge the executor waits on, an edge another path already implies included (the projection waits for the body it also reaches through the enrichment).
|
|
225
|
+
The figure's job is to show the order, so it draws the transitive reduction (`drawnDependencies(flow.nodes)`): edges `flow-edge-${from}-${to}` (`FigureEdge`, `border-control`, weight 2, ending in the figure's arrowhead `flow-svg-arrow-normal`), one per drawn dependency.
|
|
226
|
+
Each node is also described (`aria-describedby` → its `<desc>`, `flow-node-${id}-description`) by what it waits for in the drawing — `依存: <titles>`, or `依存なし (流れの最初に走る)` — so a screen reader can tell a branch (after `extract_text`) and a join (at `read_children`) from a chain.
|
|
227
|
+
- **Gates as the answer gives them.** A node's `gates` carry the flow's override already, so a flow that overrides a gate (the image flow's `ocr_document` and the media flow's `analyze_media`, their only body source, `gate_policy`) is drawn with the meaning it has there; a gate's state is read from the tenant's `featureFlags` (a flag the host did not pass is unmeasured).
|
|
228
|
+
A node's gate line (`flow-node-${id}-note-1`, also in its accessible name `<title> (<line>)`) is `gateCaption(state, policy)`: a closed state names its consequence (`門: 閉 (省略)` / `門: 閉 (失敗)`), and an open or unmeasured gated node adds its policy from `GATE_POLICY_LABELS` (e.g. `門: 開・閉じたら失敗`).
|
|
229
|
+
Nothing else is drawn on a node: the search path's circuit breaker is neither read nor fed by the pipeline.
|
|
230
|
+
- **Detail panel** (`flow-step-detail`, `data-node-id`, `data-step-key`): a `CardSection` region named by the node's title (`h3`, `flow-step-detail-heading`), the close button `flow-step-detail-close`, and a `DescriptionList` (`flow-step-definition`) of: the step and its version (`flow-step-key`, `<step> (版 <n>)`), the phase (`flow-step-phase`), the parameters as the answer gives them, one `<key>: <JSON>` line each (`flow-step-parameters`, `flow-step-parameter-${key}`, or `引数なし`, `flow-step-no-parameters`: their meaning is the step's, so no words are added),
|
|
231
|
+
every edge as the executor keeps it — every node of its `depends_on` in order (`flow-step-depends-on`), the ones the figure does not draw named apart (`flow-step-indirect-dependencies`) — and every node whose `depends_on` names it (`flow-step-dependents`),
|
|
232
|
+
and its gates (the foundation's bulleted `List`, `flow-step-gate-list`, of `flow-gate-${flag}` with `data-gate-state` and `data-when-closed`; a failing gate names its failure status, e.g. `閉じたときは FAILED_EXTRACT で失敗`; `flow-step-no-gates` for a node without one), plus `flow-gate-policy-override` when the flow overrides the step's declaration (`gate_policy`).
|
|
233
|
+
A `read_children` node, which runs each child's own flow, also lists the catalogue's child flows (`purpose: "child_reading"`, `flow-step-child-flow-list`, `flow-child-flow-${key}` with `data-flow-key`: `<title> (<key> 版 <version>): <its nodes' titles>`) and notes that the deployment's bindings, which the catalogue does not answer, decide which child takes which flow (`flow-step-child-flow-note`; `flow-step-no-child-flows` when the catalogue has none).
|
|
234
|
+
When it opens or starts describing another node, the focused element outside it is recorded as its opener: the pressed node (pressing a node — click, Enter, or Space — leaves focus on it), or, in controlled mode, the host control that held focus when `selectedNodeId` changed.
|
|
235
|
+
It closes with its close button and with Escape pressed inside it (the region's `onEscape`, the rule the history panel follows; Escape on a node clears the selection and leaves focus on that node), and either returns focus to that opener while it is still in the document; with no opener (opened with nothing focused) or one that has left the document, focus goes to the node it described.
|
|
236
|
+
- **Metrics per kind** (`flow-step-metric`, `data-kind`, an `h4` `CardSection` in the detail): only the `PipelineStepMetric` whose `documentKind` is the selected kind and whose `stepKey` is the node's step; the heading and `flow-metric-scope` state the window (`直近の集計 (<kind>・直近 <metricSampleWindow> 件)`), and a pair without a metric says so in a sentence headed by the root's unmeasured label (`未測定 (<kind> の実行でこの段を記録したものはまだありません)`, `flow-step-metric-empty`) instead of showing another kind's figures.
|
|
237
|
+
The figures sit in a `DescriptionList` (`flow-metric`, one row `flow-metric-${field}` per field holding its term and its value).
|
|
238
|
+
The sample, succeeded, failed and skipped counts (`flow-metric-sample-count-value`, `flow-metric-succeeded-value`, `flow-metric-failed-value`, `flow-metric-skipped-value`) are `Measured` counts and p50 / p95 (`flow-metric-p50-value`, `flow-metric-p95-value`) are `Measured` texts that move whole (`data-cui-keep="whole"`, `1,234 ms`), all in the root's format locale; p50 / p95 are taken over measured durations only (`flow-metric-note`, which names the same label: `所要 未測定`).
|
|
197
239
|
The last start sits under one hook either way (`flow-metric-last-started-at-value`): the time in the root's zone with `data-measured="true"`, or the root's unmeasured label with `data-measured="false"`; a p50 / p95 the metric did not measure is likewise the label.
|
|
198
240
|
`metricSampleWindow` in the heading and `flow-metric-scope` is written as a bare number: it is a count inside a sentence, so it is not `Measured`, not grouped, and does not follow the root's `formatLocale`.
|
|
199
|
-
- **MIME classification table
|
|
241
|
+
- **MIME classification table**: a `CardSection` (`h3`, `flow-mime-heading`) holding a `DataTable` (`flow-mime-table`) of the catalogue's `mime_kind_rules` in rule order, inside the foundation's table frame (`flow-mime-table-frame`, a region named by the table's caption that the keyboard can scroll), so narrow screens scroll it instead of pushing it past the card. Each row (`flow-mime-rule`, `data-kind`, `data-selected`) is a rule; the rows of the selected kind draw that kind's label as an `info` `Pill` (`flow-mime-selected-kind`).
|
|
200
242
|
|
|
201
|
-
Kind and
|
|
243
|
+
Kind and node selection are each controlled (`selectedKind` / `selectedNodeId`, with `onSelectKind` / `onSelectNode`) or uncontrolled (the first tab; a re-read catalogue that no longer runs the chosen kind shows its first tab). Required props: `catalog`, `featureFlags`, `metrics`, `metricSampleWindow` (the number of newest step records per (kind, step) the host aggregated into each metric; a positive integer, otherwise `RangeError`).
|
|
244
|
+
A controlled `selectedKind` no document flow of the catalogue runs throws `RangeError`. A host that could not read the catalogue states why under the viewer's title (`PIPELINE_FLOW_TITLE`, `取り込みパイプライン`) instead of drawing it.
|
|
202
245
|
|
|
203
246
|
```tsx
|
|
204
|
-
<PipelineFlowViewer featureFlags={flags} metrics={stepMetrics} metricSampleWindow={stepMetricWindow}
|
|
247
|
+
<PipelineFlowViewer catalog={flowCatalog} featureFlags={flags} metrics={stepMetrics} metricSampleWindow={stepMetricWindow} onSelectNode={(id) => setNode(id)} />
|
|
205
248
|
```
|
|
206
249
|
|
|
207
|
-
The pure layout lives in `layoutFlow(
|
|
208
|
-
|
|
209
|
-
`
|
|
250
|
+
The pure layout lives in `layoutFlow(nodes, geometry)` (`pipelineFlowLayout.ts`), over nodes `{ id, dependsOn }` given after the nodes they depend on (the executor's order is one such order; an empty input, a repeated id or a dependency on a later or unknown node throws `RangeError`): rank = longest dependency path; an edge that skips layers gets one virtual slot per skipped layer, the slots take part in barycenter ordering, and the edge runs through them, so no edge enters a node it does not connect. The viewer lays out `drawnDependencies(flow.nodes)`.
|
|
251
|
+
The geometry is built from `@aiquants/console-ui`'s figure formula and tokens alone (`flowGeometry(lines)`), of which this package keeps no copy: a node is as wide as the widest box `figureBoxPlan` plans for the lines given (the fixed sans text-width model widened by the most letter and word spacing WCAG 1.4.12 allows, on the 8-px grid, with `space-3` on both sides) and as high as that plan, `space-12` (48) lies between layers and `space-6` (24) between the nodes of a layer and around the figure, more than the reach of the foundation's focus ring (`FIGURE_RING_REACH_PX`, 4).
|
|
252
|
+
The viewer draws with `catalogFigureGeometry(catalog)` (`PipelineFlowViewer.tsx`), the geometry of every title and phase of the flows a kind runs with every gate line a node can show, whatever the flags: 208 × 84 for the built-in flows (the widest line is the gate line `門: 未測定・閉じた門で異なる`; a name and two notes), so the default kind's figure (`email`) is 1792 × 240 and the widest (`pdf`, `office`) 2048 × 240, all on the 4 px lattice the foundation's `Figure` requires.
|
|
210
253
|
|
|
211
254
|
### 9. `DocumentHistoryInspector`
|
|
212
255
|
|
|
213
|
-
A non-modal
|
|
256
|
+
A non-modal panel showing one document's change history across its revisions (S1 §3.3.5-13, §8.19): a console `Card` (`history-inspector`, `data-metadata-id`), the region named by its `h2`, with no `role="dialog"`. It reads through the host and draws only what the host returns.
|
|
214
257
|
|
|
215
258
|
Props: `host: { loadHistory, compare, readLines }` (`DocumentHistoryHost` without `loadChanges`; every read resolves to a `ResourceOutcome`), `metadataId` (any revision row of the document), `target: DocumentHistoryTarget` (`{ kind: "latest_state" }` for an ingest row's history, `{ kind: "run", runId }` for a recent run's detail, `{ kind: "state", stateId }` for a change's arriving state), `openRequest: number` (changed by the host on every open or re-target from outside, including a press naming what is already shown;
|
|
216
|
-
each change reads the history again, keeping the shown one until the answer settles and withdrawing it on a 403, and applies `target` to the fresh history;
|
|
259
|
+
each change reads the history again, keeping the shown one until the answer settles and withdrawing it on a 403, and applies `target` to the fresh history;
|
|
260
|
+
while the re-read is in flight, a target the shown history does not hold reads `文書の履歴を読み込んでいます…` (`history-detail-loading`, `aria-busy`) instead of being reported missing), `onClose()`, `returnFocusFallback: () => HTMLElement | null`, the optional `captureState(metadataId)` (the host's manual capture, a `ResourceOutcome<{ outcome }>`), and the variant section's `variantSets: readonly VariantSetView[]`, `variantHost: DocumentVariantHost` and `canSendVariantCommands`.
|
|
217
261
|
|
|
218
|
-
- **Header:** `h2` (`history-
|
|
219
|
-
- **Timeline (master, `MasterDetail` with a 288 px master and a 448 px detail minimum):** one group per revision, newest first; the current revision opens, older ones start closed; inside, the readable runs (with the badges of the state each produced, `本文 +12 −3`, `チャンク +2 −1 ~4`, `分類 変更`) and the states no readable run produced (`上書きの前の記録`, `手動の記録`, `記録の起点`), newest first, as `SelectableList` rows (`history-item`, `data-item-key="run:<id>"` / `"state:<id>"`).
|
|
262
|
+
- **Header:** the card's `h2` `文書の履歴 — <登録名>` (the level-2 heading of `history-inspector`; the foundation's card title carries no test id), the close button in the card's actions (`history-close`), and a `DescriptionList` (`history-document`, one row `history-document-<key>` each) with the URI, the current revision (`r3`), the confidentiality level, the number of revisions and the last change's time.
|
|
263
|
+
- **Timeline (master, `MasterDetail` with a 288 px master and a 448 px detail minimum):** one group per revision, newest first; the current revision opens, older ones start closed; inside, the readable runs (each named `実行 #<id> · <きっかけ> · <流れ>`, the flow by `pipelineFlowLabel` (`history-row-<key>-flow`), with the badges of the state each produced, `本文 +12 −3`, `チャンク +2 −1 ~4`, `分類 変更`) and the states no readable run produced (`上書きの前の記録`, `手動の記録`, `記録の起点`), newest first, as `SelectableList` rows (`history-item`, `data-item-key="run:<id>"` / `"state:<id>"`).
|
|
220
264
|
Marks are written as sentences: `失敗した実行の後の状態`, `記録されていません` (a finished run with no terminal step or a failed one), `この変化には記録されなかった実行 #n の変化も含まれます`, and `ソースは同じで、モデルの出力だけが変わりました`.
|
|
221
|
-
Restricted marks (V11) sit in their own group ahead of the revisions, `隔離されていた期間の記録 (n 件)`, closed at first, one row each (`history-restricted-mark`, `data-restricted-mark="state" | "run"`) with `隔離されていた期間の状態` / `隔離されていた期間の実行`, the time and the result only; a mark cannot be selected, pinned or returned to, and its React key is its position in the answer.
|
|
222
|
-
- **
|
|
265
|
+
Restricted marks (V11) sit in their own group ahead of the revisions, `隔離されていた期間の記録 (n 件)`, closed at first, one row each in the foundation's bulleted `List` (`history-restricted-list`, the toggle's `aria-controls` target; items `history-restricted-mark`, `data-restricted-mark="state" | "run"`) with `隔離されていた期間の状態` / `隔離されていた期間の実行`, the time and the result only; a mark cannot be selected, pinned or returned to, and its React key is its position in the answer.
|
|
266
|
+
- **Variant sets:** below the header, a `Disclosure` `変種の組での状態 (n 組)` (`history-variants`, closed at first, closed again for another document) holds the document's state in each active set; the section reads `variantHost.load(metadataId)` (`DocumentVariantReading`: the document's text domain and core's `DocumentVariantState`s) only while it is open, and again on each `openRequest`.
|
|
267
|
+
A row per active set (`history-variant-row`, `data-variant-key`, and `data-state`, which names what its state cell reads: a state, `unread`, `unclassified` or `missing`) first decides whether the set covers the document by the reading's domain (`variantSetCoverage`, the rule the processing-objects list reads too).
|
|
268
|
+
A set that does not cover it reads `未分類` (the document's domain is not known yet) or `対象外` (the set has no row of the domain) in the state column (`VARIANT_UNCOVERED_STATE_TEXTS`, never a badge) and says why there is no projection (`文書のドメインが決まっていません`, `組はこの文書のドメインの行を持ちません`).
|
|
269
|
+
A covered set shows the state as a badge (`PROJECTION_VARIANT_STATE_LABELS`: `投影済み`, `古い版を投影済み`, `投影に失敗`, `未投影`), or `状態を読んでいません` when the reading lacks the set, and `今すぐ投影` projects the document with the set's row of the document's domain (`variantHost.project(metadataId, strategyId)`, the `strategy_id` of `VariantSetView.domains`); `点を消す` (only for a set that may hold points) calls `variantHost.removePoints(variantKey, metadataId)`. The state column is as wide as the widest of these words (`VARIANT_STATE_CELL_CONTENTS`).
|
|
270
|
+
Only the pressed command is busy; a settled one is announced and reads the states again; a refused one says why under the table (`history-variant-failure-${key}`), the 423 refusal of a set with a waiting or running projection job in a sentence of its own that carries the API's reason naming the job (`variantProjectionActiveSentence`). With `canSendVariantCommands` false, both commands are unavailable from the first render, described by `history-variants-forbidden-note`.
|
|
271
|
+
- **Detail (`history-detail`, `data-selected-key`):** a console `CardSection` (not a landmark) with one `h3` (its level-3 heading, the same element whatever it shows), the selected item's summary as a `DescriptionList` (`history-state-summary` or `history-run-summary`, one row `<list>-<key>` each, the run's status `Pill` `history-run-status` with `data-status` and `data-presentation`) and the selected state's change from its predecessor (`DocumentChangeView`: a `nav` of changed facets, one `CardSection` at `level={4}` per changed facet (an `h4`, not a landmark, `data-facet`),
|
|
272
|
+
`変化なし` for the rest; the structured data's JSON values in the mono face with their line breaks and indentation kept; the text and summary as `TextDiffView`, the chunks as `ChunkChangeTable`; the assets facet's added, removed and modified items each in one flat list in the tree order of their keys, the derived tree paths, each row `<treePathLabel(path)>: <value>`).
|
|
273
|
+
Every control sits on a line of its own (a `Cluster`, or the card's actions), so no 44 px target stretches a line of text; a timeline row's lines (the run or cause, the time, each facet badge, the marks, the pin) are the row's lines, and a line mixing a `Pill` and text is one `Text` span.
|
|
223
274
|
A first state and a state whose predecessor the operator cannot read both say `直前の記録との比較はありません。` and read no comparison.
|
|
224
|
-
A run shows its summary, its error, its state's change and its step timeline (`PipelineRunSteps`, the terminal step named `文書の状態の記録`)
|
|
275
|
+
A run shows its summary, its error, its state's change and its step timeline (`PipelineRunSteps`, the terminal step named `文書の状態の記録`) behind a console `Disclosure` (`history-run-steps`, closed for every run the operator selects).
|
|
276
|
+
The timeline (`run-steps`, the foundation's ordered `List` without markers, its cards 12 px (`space-3`) apart, as in every list inside it) draws each step record as a list item (`run-step-<sequence>`, `data-status`, `data-step-key`) holding a console `Disclosure` (`run-step-card-<sequence>`; open at first when the run has at most `STEP_CARDS_OPEN_LIMIT` (8) records, otherwise only a failed step opens; the operator's toggle lasts until the run changes). Its summary is one row of labels (a `Cluster as="span"`: the name, a `対象 #<id>` pill for a step that targets another document,
|
|
277
|
+
the strategy pill `戦略 #<id>`, the status pill `run-step-status-<sequence>` and the duration `Measured`), so the status word is part of the button's name.
|
|
278
|
+
Its panel holds the duration as a `Meter` (`run-step-bar-<sequence>`, the step's duration within the run's longest measured one, `最も長い段の <n>%`; none for an unmeasured step or a run whose longest step took 0 ms), the error, and the report: the measured fields as the rows of a `DescriptionList` (`run-step-provenance-<sequence>`, one row `run-step-provenance-<sequence>-<key>` each, a count in a `Measured` count and any other value in a `Measured` text that wraps), the unmeasured fields in one line under the root's label (`data-unmeasured-note`)
|
|
279
|
+
and the raw detail in a closed `Disclosure` around a `CodeBlock`.
|
|
225
280
|
A fan-out step's child records are grouped under the step in one flat list in the tree order of their targets' tree paths (`targetTreePath`), each card named `<treePathLabel(path)> (#<id>)` (`添付 0 > 書庫の中身 3 (#412)`); the records that carry one path (the same child recorded twice in a run, which the record contract admits) share that path's item in `sequence` order, so none is dropped and the timeline does not throw; the group's `対象 <n> 件` counts the records.
|
|
226
|
-
A child record without a tree path (the path comes from a join, so a record written before or during the paths' backfill has none) is drawn
|
|
227
|
-
The timeline is grouped once per run, so drawing the same run again keeps the alert and the focus
|
|
228
|
-
- **Nodes of a child tree (`TreePathList`):** both lists above are flat by design:
|
|
229
|
-
|
|
230
|
-
|
|
281
|
+
A child record without a tree path (the path comes from a join, so a record written before or during the paths' backfill has none) is drawn after the group's one alert (`AlertMessage`, `run-step-group-pathless-<stepKey>`, which holds the reason `木の道を持たない記録 (移行の途中)` only), in a list of its own (`run-step-group-pathless-<stepKey>-list`), named `#<step record id> → #<target id>`, and the group opens; the cards sit after the alert, not in it, so `role="alert"` reads the reason alone and no card's text sits on the alert's tint; no path is made up for it and no record is dropped.
|
|
282
|
+
The timeline is grouped once per run, so drawing the same run again keeps the alert (the same element and occurrence) and the focus on a card's disclosure.
|
|
283
|
+
- **Nodes of a child tree (`TreePathList`):** both lists above are flat by design: the foundation's ordered `List` without markers with one `ListItem` per listed node, spaced by the caller's gap (`space-1` for the comparison's rows, `space-3` for the run's cards; the records that share an item are stacked 12 px apart too), in the tree's pre-order (`compareTreePaths`, whatever order the items come in). Each item is named by its path's full label (`treePathLabel`, which the list hands to the card or row it draws), and the label names every level, so the list nests nothing:
|
|
284
|
+
a tree of any depth draws at the same DOM depth (React's recursive walk of its components stays as shallow) and adds no width.
|
|
285
|
+
Only the listed nodes are drawn; no item stands in for an ancestor the list does not hold, so an unchanged ancestor never reads as added or removed. Every node's item is `<list testId>-item-<path>`, scoped by its list, so the same path in two lists (added, removed and modified, or two fan-out groups) never collides. The order and the labels are computed once per items array. The DOM order is the tree's pre-order, so focus moves through the cards and rows in the order they are drawn; the names and the targets are those of the cards and rows themselves.
|
|
286
|
+
The list paints nothing of its own: it is the foundation's list part, whose explicit `role="list"` keeps it a list for WebKit and VoiceOver. A path outside the grammar, a path listed twice or a kind with no name throws `RangeError`.
|
|
231
287
|
`比較の起点にする` pins a state (`history-pin-button`); the next state selected is compared with it, older side first; `外す` (`history-unpin`) releases it.
|
|
232
|
-
- **Text diff:** statistics (lines and graphemes, the similarity or the root's unmeasured label), attribute changes (`改行 CRLF → LF`), the hunk index (`45 行目 (p.3) +3 −1`), and hunk bodies read only when opened, `DIFF_LINE_PAGE_MAX` lines a page (`LoadMore`).
|
|
233
|
-
|
|
288
|
+
- **Text diff:** statistics (a `DescriptionList`: lines and graphemes, the similarity or the root's unmeasured label), attribute changes (`改行 CRLF → LF`, a bulleted `List` `<testId>-attributes` of `<testId>-attribute` items with `data-field`), the hunk index (`45 行目 (p.3) +3 −1`, an ordered `List` without markers named `変化の塊`, `<testId>-hunk-index`, of `<testId>-hunk-index-item` items holding the links), and hunk bodies read only when opened, `DIFF_LINE_PAGE_MAX` lines a page (`LoadMore`). Each body is a console `DataTable` (`<body>-lines`,
|
|
289
|
+
one row `<body>-line` per line with `data-line-kind`, in the foundation's 28 px compact rows, `rowSize="row-compact"`: a read-only line table, whose cells hold no control and no focus target): the line numbers (end-aligned) and the sign (`−` / `+`) in the mono face, and the text in the mono face with its spaces kept (`Text space="preserve"`), its removed and added pieces `InlineChange` fragments (`<del>` / `<ins>`, `<view>-removed` / `<view>-added`); a hunk with no line says `差分の行はありません。` instead.
|
|
290
|
+
Side by side applies from the width at which the side-by-side table shows without scrolling (`tableFrameMinInlinePx` of its six columns: the table's minimum, the rule `DataTable` lays out by, plus the table frame's 4 px on both sides; every fixed column is measured by `fixedColumnPx` from its name in the header's caption step and its mono samples: each side's line-number column, the largest line number's digits, the 48 px sign column, and a flexible text column of 40 mono characters, 336 px; `data-split-min-px`: 856 px up to 2-digit line numbers, 920 px for 6 digits);
|
|
291
|
+
narrower, the view keeps one column and says `幅が足りないため 1 列で表示しています` in a visible sentence, and the change view announces it once through the root's announcer, however many diffs it holds. It measures its width with `ResizeObserver`. Each line table is named after its facet and hunk (`本文 45 行目 (p.3) +3 −1`, the table's hidden caption); side-by-side rows are paired page by page (never across a page boundary) and keyed by their line numbers, as are one-column rows, so a page read later only appends rows and `LoadMore` focuses its first row.
|
|
234
292
|
- **Failures** are worded by `labels.failureSentence`: a 403 is `文書の履歴を閲覧する権限がありません。<原因>。` and withdraws a history already shown; any other failure is `文書の履歴を読めませんでした (<理由>)。` (`比較`, `差分の行` for the other reads) with a reload. A document with no record says so in a `role="status"` sentence.
|
|
235
|
-
- **Manual capture:** with `captureState`, `今の状態を記録` appears while the newest readable run has no state; the outcome is announced (`記録しました` / `前の記録から変化はありません`) and the history is read again.
|
|
293
|
+
- **Manual capture:** with `captureState`, `今の状態を記録` appears while the newest readable run has no state; the outcome is announced (`記録しました` / `前の記録から変化はありません`) and the history is read again. `canSendCommands` (required) says whether the operator may send it: when it is `false`, the control is unavailable from the first render and never busy, described by one note under the panel's heading (the card's description) drawn only while the control is (`history-forbidden-note`, `CAPTURE_FORBIDDEN_SENTENCE`, the same sentence a 403 answer states).
|
|
236
294
|
- **Nothing to select:** a history with restricted marks only says `選べる記録はありません。` (`role="status"`) in the detail instead of prompting a pick. The revision groups' open state belongs to the document: re-targeting another document starts again from the defaults.
|
|
237
295
|
- **Focus:** only the `h2` and the detail `h3` take focus (both `tabIndex={-1}`); pressing the row already selected moves focus to the `h3` too. When `今の状態を記録` disappears while holding focus (the recorded state was read back), focus moves to the detail `h3`, or the `h2` when nothing is selected; when a re-read's 403 withdraws content that held focus, focus moves to the `h2`. A refresh failure that repeats with the same words is announced again (its `AlertMessage` occurrence counts the failures).
|
|
238
|
-
The `h2` takes it on mount and whenever `openRequest` changes, recording the element outside the panel that held focus as the opener; an item picked in the panel's own timeline moves focus to the `h3`; a load settling moves nothing. A selected item inside a closed revision group opens the group. Pinning and unpinning move focus to the control that replaced the one holding it. Escape
|
|
296
|
+
The `h2` takes it on mount and whenever `openRequest` changes, recording the element outside the panel that held focus as the opener; an item picked in the panel's own timeline moves focus to the `h3`; a load settling moves nothing. A selected item inside a closed revision group opens the group. Pinning and unpinning move focus to the control that replaced the one holding it. Escape pressed inside (the card's `onEscape`: not during IME composition, nor in a text field holding a value) and the close button call `onClose`;
|
|
297
|
+
on unmount the panel itself returns focus to the opener while it is still in the document, otherwise to `returnFocusFallback()`.
|
|
239
298
|
|
|
240
299
|
```tsx
|
|
241
300
|
<DocumentHistoryInspector
|
|
242
301
|
host={documentHistoryHost} metadataId={entry.metadataId} target={entry.target}
|
|
243
302
|
openRequest={entry.openRequest} onClose={closeHistory}
|
|
244
303
|
returnFocusFallback={() => tabRefs.current[activeTab]} captureState={captureDocumentState}
|
|
304
|
+
canSendCommands={operatorMaySendCommands}
|
|
305
|
+
variantSets={variantSetList.variant_sets} variantHost={documentVariantHost} canSendVariantCommands={operatorMayManageVariants}
|
|
245
306
|
/>
|
|
246
307
|
```
|
|
247
308
|
|
|
248
309
|
### 10. `QdrantStorageCard`
|
|
249
310
|
|
|
250
|
-
Qdrant collections, point counts, named vector dimensions, health latency, and resource utilization.
|
|
311
|
+
Qdrant collections, point counts, named vector dimensions, health latency, and resource utilization, as a `@aiquants/console-ui` `Card` (`qdrant-storage-card`, a region named by its `h2`).
|
|
251
312
|
Required prop: `overview: QdrantStorageOverview`, the collections the host measured or the reason it could not, as one value in one of two states: `{ collections; probe_failure: null }` when measured, `{ collections: []; probe_failure }` when not. Omitting it cannot assert a measurement the host did not make, and an empty list never stands for an unreachable store.
|
|
252
|
-
A reason
|
|
253
|
-
|
|
254
|
-
The
|
|
255
|
-
|
|
313
|
+
A reason with nothing to read (`isReadableText`, the value slots' definition above) throws `RangeError`, since it would draw an empty warning beside unmeasured totals, and so does a reason that comes with collections (the type has no such state; the card refuses one that arrives through JSON instead of dropping either half).
|
|
314
|
+
The card's actions hold the health badge, the latency and the refresh button, and draw nothing when neither `health` nor `onRefresh` is given. The badge (`qdrant-health-badge`, `data-healthy`) is a tinted `Pill` that reads the console's probe vocabulary, `正常接続` or `接続エラー`, in its tone (`PROBE_STATE_TONES`: `success` or `danger`, so `success-fg` on `success-tint` or `danger-fg` on `danger-tint`): the words and the pairs `InfrastructureHealthMonitor` draws for the same probe.
|
|
315
|
+
The latency is `応答時間: <ms>` (`qdrant-latency`, the count `qdrant-latency-value` with `ms`, labelled 応答時間 like `InfrastructureHealthMonitor`'s), and the refresh button reads `更新` (`更新中…` while refreshing). The actions wrap between their items when the title line is too narrow.
|
|
316
|
+
A `TileGrid` of five `MetricTile`s holds the total points (`qdrant-total-points`), the total vectors (`qdrant-total-vectors`), the RAM and the disk in use (`qdrant-ram-usage`, `qdrant-disk-usage`) and the segment count (`qdrant-segment-count`), each value under `<tile>-value`.
|
|
317
|
+
Every count on the card is a `Measured` count in the root's format locale (the totals, the segment count, the collection count `qdrant-collections-count` under the list's heading, each collection's `collection-points-value-${collection_name}`, each named vector's dimensions `vector-tag-dense-size-${collection_name}` / `vector-tag-image-size-${collection_name}` with `次元`, and the latency), so the rows and the totals always group digits alike.
|
|
318
|
+
While `overview.probe_failure` is set the totals and the collection count are unmeasured, not `0`.
|
|
319
|
+
RAM and disk are text tiles written by the package's one byte format, the same as `DocumentIngestConsole`'s file size; the tiles are at least as wide as the widest size the format writes (`8,192.00 TiB`), so a size stays on its line.
|
|
256
320
|
A measured size is written in binary units (B below 1 KiB without decimals, so `512 B` and `1,023 B`, then KiB / MiB / GiB / TiB, powers of 1024), its unit chosen after rounding (1,048,575 bytes is `1.0 MiB`, never `1,024.0 KiB`), its mantissa grouped in the root's format locale in tabular figures (1,048,524 bytes is `1,023.9 KiB`; TiB has no upper bound, so 2^53 − 1 bytes is `8,192.00 TiB`) with `data-measured="true"`, and a size that is `null` or absent is the root's unmeasured label (`data-measured="false"`).
|
|
257
|
-
`latency_ms` follows the same clock rule as `InfrastructureHealthMonitor`'s `latencyMs`: a monotonic clock, rounded to whole ms.
|
|
258
|
-
In the header the badge, the latency (`qdrant-latency`) and the refresh button (`更新`, `更新中…` while refreshing) never break inside; the title group takes the width the button leaves and wraps between its items, so in a narrow card the badge and the latency move under the title whole and the button stays at the end of the first line.
|
|
321
|
+
`latency_ms` follows the same clock rule as `InfrastructureHealthMonitor`'s `latencyMs`: a monotonic clock, rounded to whole ms.
|
|
259
322
|
Every figure follows the console's one validity rule: `null` or absent means not measured (`ram_bytes`, `disk_bytes` and `segment_count` are typed `number | null`, like every measured count of `@aiquants/enrichment-core`), and a number — `points_count`, `vectors_count`, a named vector's `size`, `segment_count`, `ram_bytes`, `disk_bytes` or `latency_ms` — must be a non-negative safe integer: a negative, fractional or non-finite figure is a broken probe, so the card throws `RangeError` instead of writing it as a value or as unmeasured.
|
|
260
323
|
The card asks `@aiquants/console-ui`'s `formatCount` to judge every figure, keeping no copy of the rule; for a figure it sums, converts or writes per row, the error names the figure in front of `formatCount`'s message (`ram_bytes: …`, `points_count of collection <name>: …`, `vectors_config.dense.size of collection <name>: …`), with that error as its `cause`, and a broken latency throws `formatCount`'s own error through `Measured`.
|
|
261
324
|
Each collection's `points_count` and `vectors_count` is checked before it is summed, so a broken row (`-1` and `1001`) never hides inside a total that looks valid (`1,000`).
|
|
262
|
-
The collections
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
With `selection` (`{ selectedCollectionName, onSelectCollection, detailId }`, one value, so a host cannot pass the selected name without the handler or the detail region)
|
|
266
|
-
|
|
267
|
-
The console stylesheet draws
|
|
268
|
-
and
|
|
269
|
-
|
|
270
|
-
The row stays a table row whose cells keep their column headers, and the name column (header, button text and plain names alike) starts at the button's `space-3` inset.
|
|
271
|
-
Without `selection` the names are text and every row is `data-selected="false"`.
|
|
325
|
+
The collections are a `CardSection` (`qdrant-collections`, its `h3` コレクション一覧 over the collection count). A list that could not be read is its reason in a warning `Notice` (`qdrant-probe-failure`); otherwise the collections are drawn under one hook, `qdrant-collections-list`, whose empty text is 登録されているコレクションはありません。 (`qdrant-collections-list-empty`).
|
|
326
|
+
Without `selection` they are a read-only `DataTable` named コレクション一覧, in its focusable scroll frame (`qdrant-collections-list-frame`, a region of the same name): rows `collection-row` with `data-collection-name`, the name column as wide as the longest name (`collection-name-${collection_name}`), so a name is not broken while a narrow card scrolls the table instead,
|
|
327
|
+
the point counts end-aligned with their header, and the named-vector tags (`vector-tag-dense-${collection_name}`, `vector-tag-sparse-${collection_name}` as `Sparse` or `Sparse (<MODIFIER>)`, `vector-tag-image-${collection_name}`), neutral `Pill`s that wrap between one another in a `Cluster` (`vector-tags-${collection_name}`, absent for a collection without a named vector).
|
|
328
|
+
With `selection` (`{ selectedCollectionName, onSelectCollection, detailId }`, one value, so a host cannot pass the selected name without the handler or the detail region) they are `@aiquants/console-ui`'s `SelectableList` named コレクション一覧: each row is one `RowButton` (`collection-select` with `data-collection-name`) holding the name and a phrasing `Cluster` (`as="span"`, `collection-facts-${collection_name}`, as a button's content holds only phrasing content) of the domain, the point count and the tags, so its accessible name reads all of them;
|
|
329
|
+
`data-selected` and `aria-expanded` say whether it is `selectedCollectionName`, and `aria-controls` names `detailId`, the id of the host's region that shows the selected collection (render it while the card is drawn). One press of a row (Enter, Space or a click anywhere in it) selects once, and Tab moves from row to row.
|
|
330
|
+
The console stylesheet draws the rows' focus ring (2 px of the `focus` token, 2 px out: 5.25:1 on the row's surface and 4.82:1 on the selected one in the light scheme, 6.76:1 and 5.60:1 in the dark one)
|
|
331
|
+
and the selected look: the `info-tint` surface, which is only 1.09:1 (dark: 1.21:1) against the row's surface, and a 4 px `info-accent` bar at the start edge (4.82:1 on the tint, dark: 5.60:1), so the selection is not shown by colour alone, and the bar is redrawn in `Highlight` under forced colours. `tests/colorContrast.spec.tsx` recomputes these four ratios in both schemes from `@aiquants/console-ui`'s `TOKENS` with its `contrastRatio`.
|
|
332
|
+
A row's texts use only the roles drawn on both surfaces (`fg` and `fg-muted` on `surface` and `info-tint`).
|
|
272
333
|
|
|
273
334
|
### 11. `SyncStatusMonitor`
|
|
274
335
|
|
|
275
|
-
`sync_status` breakdown with individual and bulk retry controls
|
|
276
|
-
`
|
|
336
|
+
`sync_status` breakdown with individual and bulk retry controls, as a `@aiquants/console-ui` `Card` (`sync-status-monitor`, a region named by its `h2` エンリッチメント同期ステータス). Required props: `counts`, `totalFiles`, each figure a non-negative safe integer (a status absent from `counts` counts none; a broken figure throws `RangeError` naming it, `counts.<status>` or `totalFiles`, with `formatCount`'s error as its `cause`);
|
|
337
|
+
`failedItems` is `readonly FailedSyncEntry[]`, readable rows `{ kind: "readable", item: FailedSyncItem }` and restricted marks (`RestrictedFailureMark`, V11) drawn as one row with `隔離 (RESTRICTED) の文書`, the sync status and the failure time only, without a retry.
|
|
338
|
+
The card's actions hold the refresh (`sync-refresh-button`) and the bulk retry (`retry-all-button`).
|
|
339
|
+
A `TileGrid` of six `MetricTile`s follows: the deployment's documents (`total-files`, with `件`), the completion rate (`success-rate`, a text tile: the synced share to one decimal, or `対象なし` when there is no document, since `0.0%` would read as every document failed)
|
|
340
|
+
and the sync-status groups `status-card-synced`, `status-card-processing` (extracting, enriching and projecting), `status-card-pending` and `status-card-failed`, each value under `<tile>-value` and each group tile carrying `data-count`; the synced tile is `success` and the failed tile `danger` while their counts are positive, and every other tile is `neutral`.
|
|
341
|
+
The failure list is a `CardSection` (`failed-items-section`) whose `h3` counts its rows, grouped in the root's format locale (`同期失敗アイテム一覧 (n 件)`, or `(上位 n 件 / 全 m 件)` while the list holds the first rows of more failures than it shows). Its rows are a `DataTable` named 同期失敗アイテム一覧 (`failed-items-table`, its frame `failed-items-table-frame`): rows `failed-item-row` with `data-item-id` and `data-file-uri`, or `data-restricted-mark="failure"` and `data-position` for a mark;
|
|
342
|
+
the file's name over its URI (`failed-item-uri-${id}`), the status in a `danger` `Pill` (`failed-item-status-${id}`, `failed-item-restricted-status-${position}`), the error in `danger-fg` (`failed-item-error-${id}`, `不明なエラー` when the record holds none), the failure time and the row's retry (`retry-item-button-${id}`). The rows hold controls (`rowSize="row-control"`, 52 px) while `onRetryItem` is given, and an empty list is the table's empty text (`failed-items-table-empty`, 現在、同期失敗アイテムはありません。).
|
|
343
|
+
`listRefusal?: string` replaces the list with the refusal sentence in a warning `Notice` (`failed-items-refused`), with no row, no empty sentence, no row retry and no count in the list's heading, while the status tiles stay; each failed item's `failed_at` (an ISO 8601 instant with an explicit offset) is written in the root's zone under `failed-item-time-value-${id}` inside `failed-item-time-${id}`, the same clock as every other panel, and `null` reads `記録なし`. The monitor reads the root, so it renders inside `ConsoleUiRoot`.
|
|
344
|
+
Only the retry whose own request is in flight is busy (`onRetryAll` / `onRetryItem` returned a promise that has not settled: the bulk retry reads `再試行中...`, a row's retry `<file name> (<URI>) を再試行中...`); while a retry is sent (`isRetrying`) the other retries take no press and are described by `RETRY_SENDING_TEXT`, drawn beside the bulk retry and once above the failure table for the rows (`failed-items-sending-note`). With nothing failed the bulk retry says `NO_FAILURES_TO_RETRY_TEXT` instead, the lasting reason before the passing one. `canSendCommands` (required)
|
|
345
|
+
says whether the operator may send the retries at all: when it is `false`, every retry is unavailable from the first render and never busy, described only by one note under the title (the card's description, `sync-status-monitor-forbidden-note`, `RETRY_FORBIDDEN_TEXT`) drawn while some retry control is, instead of the passing reasons above.
|
|
277
346
|
`onShowAllFailures?: () => void` draws `全ての失敗を見る` (`failed-items-all-failures`, a `Link`) below the list's heading and calls it when pressed, unless the list is refused; the list here holds its first rows only.
|
|
278
347
|
|
|
279
348
|
### 12. `DocumentChangeFeed`
|
|
@@ -287,24 +356,79 @@ The recent document changes (S1 §3.3.5-4), a full-width `Card` `直近の文書
|
|
|
287
356
|
### 13. `ProcessingObjectBrowser`
|
|
288
357
|
|
|
289
358
|
The processing-objects list, a full-width `Card` `処理オブジェクト一覧` that reaches every `td_file_metadata` row of the tenant the operator may read (the ingested documents and the nodes of their trees), newest first, read a keyset page at a time (`PROCESSING_OBJECT_PAGE_SIZE` rows, `LoadMore`) through `@aiquants/enrichment-core`'s processing-objects contract.
|
|
290
|
-
Props: `host: ProcessingObjectHost` (`{ loadPage(filter, cursor, signal) }`, `processingObjectHost.ts`), the controlled `filter: ProcessingObjectFilter` with `onFilterChange(filter)`, and the optional `onOpenHistory(metadataId)` (without it there is no `履歴` column).
|
|
359
|
+
Props: `host: ProcessingObjectHost` (`{ loadPage(filter, cursor, signal) }`, `processingObjectHost.ts`), `mimeKindRules` (the flow catalogue's `mime_kind_rules` in rule order, which name a document row's kind; required, the list has no rules of its own), the controlled `filter: ProcessingObjectFilter` with `onFilterChange(filter)`, `variantSets: readonly VariantSetView[]` (the tenant's variant sets), and the optional `onOpenHistory(metadataId)` (without it there is no `履歴` column).
|
|
291
360
|
|
|
292
361
|
- The filters: three `SegmentedControl`s (`processing-objects-kind` / `-revision` / `-deleted`), a `Select` of the sync status (`processing-objects-status`; a node is filtered by its root's status) and a name query form (`processing-objects-query`, `絞り込む`).
|
|
293
362
|
Each control asks the host for the whole filter with the one key changed (pressing the pressed segment asks nothing); the query is sent trimmed when the form is submitted, a blank query drops the query, and a query over `PROCESSING_OBJECT_QUERY_MAX_CHARS` characters is not sent and shows the field's error. The labels are `PROCESSING_OBJECT_KIND_LABELS`, `PROCESSING_OBJECT_REVISION_LABELS`, `PROCESSING_OBJECT_DELETION_LABELS` and `PROCESSING_OBJECT_STATUS_LABELS`.
|
|
294
363
|
- A changed filter reads the list again from its first page (the paged key is the filter). The first page must carry the total (`ProcessingObjectPage.total`); a first page without one breaks the host's contract and throws to the nearest error boundary.
|
|
295
364
|
- The count `全 N 件のうち M 件を表示しています。` (`processing-objects-count`) is written above the table and announced (`useAnnounce`) whenever it changes.
|
|
296
|
-
-
|
|
365
|
+
- Each active variant set adds, above the table, a meter of its projected share (`processing-objects-variant-coverage`, `processing-objects-coverage-${key}`: Σprojected / Σdocuments over the set's rows, `87.5% (7 / 8 件)`, or `対象の文書がありません`), and a column `組 <key>` with a document's state in the set (`processing-object-variant-${id}-${key}`).
|
|
366
|
+
Whether the set covers the document is decided first, by the document's text domain (`ProcessingObjectItem.domainKey`, `variantSetCoverage`): a document it does not cover reads `未分類` (no domain yet) or `対象外` (the set has no row of the domain), never a badge, even when the page carries a state for the set; a covered one reads its state's badge with `data-state`, or `状態を読んでいません` when the page lacks the set.
|
|
367
|
+
The column is as wide as the widest of these words (`VARIANT_STATE_CELL_CONTENTS`); a node and a restricted mark leave it empty.
|
|
368
|
+
- A row (`processing-object-row`, `data-row-kind` `document` / `node` / `restricted`) shows the id, the kind (`文書 (<document kind>)`, the kind `classifyDocumentKind(mimeKindRules, mimeType)` gives the recorded MIME type, `その他` for a type no rule names; or the asset type's name), a node's place (`treePathLabel` and `根 <id>`), the name (a document's name and URI, a node's `sourceName` or `名前なし`), the classification and revision (`CONFIDENTIALITY_LEVEL_LABELS`, `r<n>`, `旧版`, `削除済み`), the status and the times (`登録` / `更新`, `RecordedInstant`).
|
|
297
369
|
A document's status is its sync status badge (`SYNC_STATUS_LABELS`, `syncStatusTone`) or `取り込みの記録なし`, and the error of a failure; a node's is `根の実行` with its root's sync status and `読み: <step> <status> (<skip reason>)` (`nodeReadingText`, `読みの記録なし` when the root's latest run did not read it).
|
|
298
370
|
`履歴` opens the document's history (`文書の履歴`), and a node's opens its root's (`根の履歴`); the button names its object to assistive technology.
|
|
299
371
|
- A restricted mark is one row with `隔離 (RESTRICTED) の文書`, its sync status and its times only, keyed by its position. A list narrowed by name holds no mark (the host's contract).
|
|
300
372
|
- A failed re-read or later page keeps the rows shown and says why (`processing-objects-refresh-failed`); a later page's 403 re-reads the first page, and a 403 there withdraws the list (`処理オブジェクトを閲覧する権限がありません。`).
|
|
301
373
|
|
|
374
|
+
### 14. `ChildRunList`
|
|
375
|
+
|
|
376
|
+
The child flows' runs under one root run of the recent runs table (S1 §3.3.5-13), a `DataTable` read a keyset page at a time (`CHILD_RUN_PAGE_SIZE` rows, `LoadMore`) through `@aiquants/enrichment-core`'s recent runs contract (`ChildRunPageSchema`).
|
|
377
|
+
Props: `rootRunId`, `read: ChildRunPageReader` (`(rootRunId, after, signal) => Promise<ResourceOutcome<ChildRunPage>>`; the first page with `after` `null`, a later page after the `next` the previous page named), `caption` (the table's name for assistive technology, such as `実行 #40 の子の実行`), `rescueTarget()` (where focus goes when the list is withdrawn, the control that opened it) and `testId`.
|
|
378
|
+
|
|
379
|
+
- A row (`<testId>-row`, `data-run-id`, `data-status`) shows the node the run ran for as the processing-objects list names a node (its place, `treePathLabel` and `#<row id>`; its name, `sourceName` or `名前なし` (`UNNAMED_NODE_TEXT`)), the flow (`pipelineFlowLabel`), the status badge (`PIPELINE_RUN_STATUS_PRESENTATION` with `PIPELINE_RUN_STATUS_TONES`, so a running run waiting for its retry reads `再試行待ち`), the step it stopped at (`ledgerStepLabel`, or `—`) and its start and finish times.
|
|
380
|
+
- The host answers only the child runs of nodes the viewer may read, never a mark; an empty page reads `読める子の実行はありません。`.
|
|
381
|
+
- A failed re-read or later page keeps the rows shown and says why (`<testId>-refresh-failed`); a later page's 403 re-reads the first page, and a 403 there withdraws the list (`子の実行を閲覧する権限がありません。<原因>。`, `CHILD_RUNS_REFUSAL`).
|
|
382
|
+
|
|
383
|
+
### 15. `IngestionQueueHealthCard`
|
|
384
|
+
|
|
385
|
+
The health screen's card of the deployment's ingestion queue for the tenant (`IngestionQueueHealth` of `@aiquants/enrichment-core`, the API's `GET /ai/enrichment/admin/queue` measured once by the database's clock).
|
|
386
|
+
Props: `reading: IngestionQueueHealthReading` (`{ kind: "measured", health }`, `{ kind: "forbidden" }` for an operator the upstream refuses, or `{ kind: "unavailable", status }` for a read that answered nothing usable, `status` `null` without one).
|
|
387
|
+
|
|
388
|
+
- The counts (`ingestion-queue-counts`): the due jobs (`取り出しを待つ仕事`), the oldest due job's wait in seconds and since when (`最も古い待ち`, `なし` while nothing is due), the jobs waiting for a retry (`再試行の待ち`), the running jobs (`実行中`), those asked to stop (`取り消しを求めた実行中`), those whose lease ran out (`持ち分の切れた実行中`) and when it was measured (`測った時刻`), every count a `Measured` in the root's format and every time a `RecordedInstant` in the root's zone.
|
|
389
|
+
- While a running job's lease has run out, a warning names how many and that the patrol reaps them (`ingestion-queue-expired-leases`).
|
|
390
|
+
- The holders (`ingestion-queue-holders`, a row per owner with `data-owner`): the owner, how many running jobs it holds and its newest heartbeat; with none, `今、仕事を持つワーカーはありません (ワーカーは走らせる仕事を通してだけ見えます)。`, since an idle worker leaves no row.
|
|
391
|
+
- In place of the counts, a refusal reads `取り込みの列の様子を読む権限がありません。` (`INGESTION_QUEUE_HEALTH_FORBIDDEN_SENTENCE`, a warning `Notice`) and an unreadable queue `取り込みの列の様子を読めませんでした (HTTP <status>)。` (`ingestionQueueHealthUnavailableSentence`, an `AlertMessage`).
|
|
392
|
+
|
|
393
|
+
### 16. `IngestionJobBoard`
|
|
394
|
+
|
|
395
|
+
The ingest tab's board of the deployment's moving ingestion jobs (S1 §3.3.5-6, `@aiquants/enrichment-core`'s moving jobs contract): one card (`動いている仕事`, `ingestion-job-board`) with a section of the running jobs (`実行中`, `ingestion-jobs-running`) and of the waiting ones (`待ち`, `ingestion-jobs-waiting`), each a `DataTable` read a keyset page at a time (`INGESTION_JOB_PAGE_SIZE` rows, `LoadMore`), newest first.
|
|
396
|
+
Props: `read: IngestionJobPageReader` (`(state, after, signal) => Promise<ResourceOutcome<IngestionJobPage>>`; `state` `RUNNING` or `QUEUED`, the first page with `after` `null`), `revision` (advanced by the host on the jobs' signals).
|
|
397
|
+
Also `canSendCommands` (whether the operator may send a cancel at all; when false each row's 取り消す is unavailable from the first render, described by one note before the sections, `ingestion-job-board-forbidden-note`, `INGESTION_JOB_CANCEL_FORBIDDEN_SENTENCE`, drawn while some section draws a 取り消す; no confirmation opens), `isBusy` (a submission of the host is in flight), `cancellingJobId` (the job whose cancel is in flight, or `null`), `onCancel(job)` and `cancelAnswer` (the last cancel's answer, `IngestionJobCancelAnswerSchema`'s job, or `null`).
|
|
398
|
+
|
|
399
|
+
- A row (`<section>-row`, `data-job-id` and `data-state`, or `data-restricted-mark="job"` for a mark) shows what the job acts on (`ingestionJobTargetLabel`: `「<name>」(r<revision>・#<row>)` with `旧版` for an older revision, or the kind for a job on no single document), its kind (`ingestionJobKindLabel`: `取り込み` / `取り下げ` / `日報の照合` / `変種の組の投影`, `知らない種類 (<kind>)` otherwise), its state (`ingestionJobStatus`: `待ち`, `再試行の待ち`, `実行中`, `取り消しを求めた実行中`), its attempts (`<started> / <most>`), when it was enqueued, when a worker may take it next (waiting jobs only, `—` while running),
|
|
400
|
+
its last failure (`—` without one) and 取り消す while it waits or runs and no cancel was asked.
|
|
401
|
+
- A restricted mark shows `RESTRICTED_DOCUMENT_TEXT`, its state and its enqueue time only, and offers no cancel; a job whose document the row scope hides is not on the board. An empty section reads `読める<section>の仕事はありません。`, and an empty page that is still followed `ここまでに読める<section>の仕事はありません。続きを読み込めます。`.
|
|
402
|
+
- 取り消す (`aria-expanded`, `aria-controls`) opens the section's confirmation above its table (`<section>-confirm`, a named group) with `ingestionJobCancelSentence` and focus on 取り消しを確かめる (`danger`), which calls `onCancel`; やめる closes it and returns focus to 取り消す. No table cell holds an unavailable control: the confirmation's button tells why it cannot send (`他の送信の答えを待っています`, `別の仕事の取り消しの答えを待っています`) and is busy (`取り消しを送っています…`) while its own cancel is in flight.
|
|
403
|
+
- Each new `cancelAnswer` is applied once in place (a job that ended leaves its section; a running job whose stop was asked shows `取り消しを求めた実行中` and keeps its document) and announced (`ingestionJobCancelAnnouncement`); an answer equal to the last one is neither applied nor announced again. Focus on a control that disappears returns to the confirmation's opener when it remains, else to the section's heading.
|
|
404
|
+
- Each `revision` re-reads a section's first page, a refused one too (a 403 offers no reload, so the next revision brings it back once the refusal is lifted), except a section whose later pages the operator read: it keeps them and offers `最新の一覧に読み直す` (`<section>-stale`). A failed re-read or later page keeps the rows shown and says why (`<section>-refresh-failed`); a later page's 403 re-reads the first page, and a 403 there withdraws the section (`動いている仕事を閲覧する権限がありません。<原因>。`, `INGESTION_JOBS_REFUSAL`).
|
|
405
|
+
|
|
406
|
+
### 17. `EmbeddingVariantBoard`
|
|
407
|
+
|
|
408
|
+
The board of the tenant's embedding variant sets (the API's `GET /ai/enrichment/admin/variants`, `VariantSetList`): one card `埋め込みの変種の組` (`embedding-variant-board`) with a `CardSection` per set (`variant-set-${key}`, `data-active`, `data-auto-project`; its `h3` `組 <key>` and its identity label) and the create form (`variant-create`). Props: `variantSets: readonly VariantSetView[]`, `commands: VariantSetCommands` (`variantHost.ts`) and `canSendCommands`. The host re-reads the list after a command; the board holds no copy of a set.
|
|
409
|
+
|
|
410
|
+
- **A set:** its embedding (`variant-set-embedding-${key}`: the provider by `EMBEDDING_PROVIDER_LABELS`, the model, the dimension, the query prefix quoted so its trailing space shows, the passage template, the sparse modifier by `SPARSE_MODIFIER_LABELS`, the endpoint and its key's variable (`無し (宛先の決まった種類)` without one), the identity) and its projected share (`variant-set-coverage-${key}`, a meter of Σprojected / Σdocuments, or `対象の文書がありません`);
|
|
411
|
+
the switches `自動の投影` and `有効` (`variant-set-auto-project-${key}`, `variant-set-active-${key}`), each sending only the switch it changes (`commands.patch(key, { auto_project })` / `{ is_active }`); the rows per text domain (`variant-rows-${key}`, `variant-row-${key}` with `data-domain-key` and `data-strategy-id`: the domain with its collection and indexing logic, the strategy, `文書 n 件 · 投影済み n · 古い版 n · 失敗 n` and the row's share).
|
|
412
|
+
- **Projection:** the scope (`variant-set-scope-${key}`, `未投影の文書だけ` first, or `全ての文書をもう一度`) and `投影を始める` (`variant-set-project-${key}`, `commands.startProjection(key, { scope })`; unavailable for an inactive set). The 202 answer is noted under the set (`variant-set-noted-${key}`) with its job, or that it merged into a job already waiting or running.
|
|
413
|
+
- **Removing every point:** `全ての点を消す` (`variant-set-remove-${key}`, `aria-expanded`) opens a confirmation group (`variant-set-confirm-${key}`) with focus on `全ての点を消すことを確かめる` (`danger`), which calls `commands.removePoints(key)`; `やめる` closes it. Focus on a confirmation that disappears returns to its opener, or to the card's heading when the set left the list. A 423 refusal (a projection job of the set is waiting or running) is the sentence `variantProjectionActiveSentence` with the API's reason naming the job.
|
|
414
|
+
- **Create:** every field of `VariantSetCreateRequest` (`variant-create-key`, `-provider`, `-model`, `-dimension`, `-query-prefix`, `-passage-template`, `-sparse-modifier`, `-base-url`, `-api-key-env`, `-auto-project`); the texts are sent as typed, an empty optional text as `null` and the dimension as a number. The form is checked with the core's `VariantSetCreateRequestSchema` before it is sent, and each refused field says why (`VARIANT_SET_FIELD_MESSAGES`) once a send has been attempted.
|
|
415
|
+
A refusal the host reads (`VariantSetCreateRejection`) puts the API's 422 messages at the fields they name and the rest in `variant-create-rejected`, and an endpoint outside the deployment (403 / 4033) at the URL field (`ENDPOINT_OUTSIDE_DEPLOYMENT_TEXT`); any other failure is `variant-create-failure`. A created set is announced and the form empties.
|
|
416
|
+
- Only the pressed control is pending while its request is in flight (a switch draws `切り替えを送っています…` beside it, a button is busy); each set's last outcome is noted or alerted under it (`variant-set-failure-${key}`). With `canSendCommands` false, every control is unavailable from the first render, described by `variant-board-forbidden-note` (`VARIANT_COMMAND_FORBIDDEN_SENTENCE`), and nothing is sent.
|
|
417
|
+
|
|
418
|
+
## Variant Sets (`variantPresentation.ts`, `variantHost.ts`)
|
|
419
|
+
|
|
420
|
+
The words every variant component draws, each a total map over `@aiquants/enrichment-core`'s vocabulary: `PROJECTION_VARIANT_STATE_LABELS` and `PROJECTION_VARIANT_STATE_TONES`, `EMBEDDING_PROVIDER_LABELS`, `SPARSE_MODIFIER_LABELS`, `COMPARISON_MODE_LABELS`, `VARIANT_PROJECTION_SCOPE_LABELS`, with `PRODUCTION_VARIANT_LABEL` / `variantKeyLabel`, `variantSetOptionLabel`, `VARIANT_STATE_UNREAD_TEXT`, `VARIANT_UNCOVERED_STATE_TEXTS`, `VARIANT_SET_NO_DOCUMENT_TEXT`, `VARIANT_COMMAND_FORBIDDEN_SENTENCE` and `variantProjectionActiveSentence`.
|
|
421
|
+
Whether a set covers a document is one rule, `variantSetCoverage(set, domainKey)` (`VariantSetCoverage`): `unclassified` when the document's domain is `null`, `missing` when no row of the set (`domains[].domain_key`, matched exactly) is the domain, else `covered` with that row (the strategy row a projection names).
|
|
422
|
+
`ProcessingObjectBrowser` and the history panel's variant section both read it: an uncovered set's state cell is the word of its kind (`VARIANT_UNCOVERED_STATE_TEXTS`: `未分類`, `対象外`), and both state columns are measured from `VARIANT_STATE_CELL_CONTENTS` (every state's badge, `状態を読んでいません`, `未分類` and `対象外`).
|
|
423
|
+
`activeVariantSets(sets)` picks the sets a search, a comparison, a document's states and the processing-objects list name. A set's share is one formula, `variantCoverage(rows)` (Σprojected / Σdocuments over its rows, `null` when they count no document), written by `variantSharePercentText` rounded down to `VARIANT_SHARE_DECIMALS` (1) decimals on the integer counts, so `100.0%` means every document is projected, and by `variantCoverageText` with the counts in the root's format.
|
|
424
|
+
The host contracts are `VariantSetCommands` (the board's four commands: `create` → `CommandOutcome<VariantSetView, VariantSetCreateRejection>`, `patch` → `ResourceOutcome<VariantSetView>`, `startProjection` → `ResourceOutcome<VariantProjectionAccepted>`, `removePoints` → `CommandOutcome<null, VariantProjectionActiveRejection>`) and `DocumentVariantHost` (`load` → `ResourceOutcome<DocumentVariantReading>`, `project(metadataId, strategyId)` → `ResourceOutcome<DocumentVariantProjection>`, `removePoints(variantKey, metadataId)` → `CommandOutcome<null, VariantProjectionActiveRejection>`).
|
|
425
|
+
|
|
302
426
|
## Search Exchange Shapes (`documentSearch.ts`)
|
|
303
427
|
|
|
304
|
-
The shapes the
|
|
428
|
+
The shapes the document search exchanges with its host, field for field the public API's answers, and the words both search workbenches are drawn with:
|
|
305
429
|
`DocumentSearchParams` (`threshold` is sent with `DENSE` only; `fusion`, `rrf_k` and `alpha` with `HYBRID` only), `DocumentSearchAnswer` (`hits: DocumentSearchHit[]`, `authz_scopes`, `search_metadata: DocumentSearchMetadata`), `DocumentSearchHit` (every column `null` when the caller's scope hides it, `graph_relations` `null` while a row scope applies), `DocumentScores` (`fused`, `vector: DocumentVectorScore | null`, `lexical_rank`, `graph_rank`),
|
|
306
|
-
`
|
|
307
|
-
`degradation_reasons` is `readonly string[]`, not the closed vocabulary, so an answer from a newer API that adds a code is still taken and the code shown as it is.
|
|
430
|
+
and `DocumentSearchParams.variant_key` (sent only for a set other than production). The comparison exchanges the core's `VariantComparisonRequest` and `VariantComparisonAnswer` as they are.
|
|
431
|
+
The document search's `degradation_reasons` is `readonly string[]`, not the closed vocabulary, so an answer from a newer API that adds a code is still taken and the code shown as it is; `DEGRADATION_REASON_LABELS` words every code of the core's vocabulary, `dense_embedding_unavailable` (the query's embedding endpoint was not reached, so the dense tier was skipped) included.
|
|
308
432
|
The words: `SEARCH_MODE_LABELS`, `SEARCH_FUSION_LABELS`, `QUALITY_TIER_LABELS`, `BREAKER_STATE_LABELS`, `DEGRADATION_REASON_LABELS` / `degradationReasonText`, `HIDDEN_FIELD_TEXT`, and the value helpers `vectorScoreText`, `SCORE_DECIMALS` (3), `rankAgreement` / `RankAgreement` and `variantHitKey`.
|
|
309
433
|
|
|
310
434
|
## Colour Contrast
|
|
@@ -312,36 +436,45 @@ The words: `SEARCH_MODE_LABELS`, `SEARCH_FUSION_LABELS`, `QUALITY_TIER_LABELS`,
|
|
|
312
436
|
`tests/colorContrast.spec.tsx` checks the parts of the console drawn through `@aiquants/console-ui`'s colour roles against WCAG 2 AAA in both colour schemes: text needs 7:1 (1.4.6) and graphics 3:1 (1.4.11), both read from `@aiquants/console-ui`'s `CONTRAST_MINIMUM`, and the ratio is its `contrastRatio`, the repository's only contrast computation.
|
|
313
437
|
The package keeps no luminance computation or contrast bar of its own, the colour spec included.
|
|
314
438
|
|
|
315
|
-
- **History screens and the processing-objects list:** `DocumentHistoryInspector
|
|
439
|
+
- **History screens and the processing-objects list:** `DocumentHistoryInspector` (with its `DocumentChangeView` and `TextDiffView`), `DocumentChangeFeed` and `ProcessingObjectBrowser` paint their colours only through `@aiquants/console-ui`'s roles; their specs run `scanColourRoles` over the rendered panel, so every text sits on a pair `CONTRAST_PAIRS` lists for both schemes (the diff pieces are `fg` on `danger-tint` / `success-tint`, drawn by `InlineChange`).
|
|
440
|
+
Every state the inspector, its comparison view and its text diff draw (loading, read, empty, a first read refused or failed, a refresh failure, a capture in flight or refused, the forbidden note, the restricted group open with a pin, a selection missing, nothing to select; the diff closed, unmeasured, side by side, in one column, loading, failed, refused, a failed next page, a chunk pair; every facet form) passes the scan as exactly that state's set of pairs.
|
|
316
441
|
- **Search tab:** `DegradedSearchBanner`, `MetadataSearchWorkbench` and `ModelComparisonWorkbench` paint nothing themselves (no class, no style); their specs run `scanColourRoles` over the console root with an answer drawn (the degraded note, the run, the hit list and the detail; the comparison's agreement, a variant's warning and the grid), so every text sits on a pair `CONTRAST_PAIRS` lists for both schemes.
|
|
317
442
|
- **Probe-state marks:** the health monitor's overall badge, each probe's badge, and each probe's error text (`service-error-${service}`), with every probe connected and with some probes failed (a connected one among them carrying an error text), each at rest and while re-probing, with and without the refresh button; the Qdrant card's health badge (`qdrant-health-badge`), connected and failed, at rest and while refreshing.
|
|
318
|
-
Each mark's texts pass `@aiquants/console-ui/testing`'s role scan `scanColourRoles` as exactly its own pair, its state's `success-fg` on `success-tint` or `danger-fg` on `danger-tint`, and the error text's `danger-fg` on `
|
|
319
|
-
No class inside the
|
|
320
|
-
On the
|
|
321
|
-
|
|
443
|
+
Each mark's texts pass `@aiquants/console-ui/testing`'s role scan `scanColourRoles` as exactly its own pair, its state's `success-fg` on `success-tint` or `danger-fg` on `danger-tint`, and the error text's `danger-fg` on the card's `surface` in either state (pairs `@aiquants/console-ui`'s `CONTRAST_PAIRS` lists for both schemes, so each meets the text bar). The health monitor's marks draw no graphic (no icon, no dot): the word alone carries the state.
|
|
444
|
+
No class inside the Qdrant card's badge can change what it paints (a colour, `opacity`, a filter, a blend mode, a mask, an animation or any arbitrary property such as `[color:…]`, under any variant).
|
|
445
|
+
On the card's elements around it there is no compositing class (`opacity`, a filter, a blend mode, a mask, an animation), no arbitrary property, and no class that paints the elements inside (a colour or compositing utility under a variant that reaches them: `*:`, `**:`, `marker:`, `selection:` or an arbitrary variant with `&` such as `[&_p]:`), which would set the badge's paint directly, whatever the order of the host's layers.
|
|
446
|
+
- **Pipeline tab:** every state `EnrichmentStrategyBoard` (read with every gate state, a tenant and a deployment-default row and an inactive strategy; empty; the forbidden note; a change in flight) and `InfrastructureHealthMonitor` (all connected; some failed with error texts and unmeasured values; re-checking; without the button) draw passes `scanColourRoles` over the card as exactly that state's set of pairs (the gate words are `success-fg`, `warning-fg` or `fg-muted` on the column header's `subtle`).
|
|
447
|
+
Every state `PipelineFlowViewer` draws (read with every gate state as a node tone; a selected node with its step, gates, override and measured figures; a node without gates or a metric; an unmeasured gate's node with unmeasured values)
|
|
448
|
+
passes `scanColourRoles` over the viewer as exactly that state's set of text pairs and of line pairs (`measuredGraphics`: a plate's accent on the figure's `surface` and on its own tint, `border-control` for the edges, the arrowheads and an unmeasured gate's plate, `focus` for the rings), reading every SVG shape of the figure (`graphics`); a node's notes are `fg`, the one text role every face a node takes carries.
|
|
449
|
+
- **Monitoring cards:** every state `SyncStatusMonitor`, `QdrantStorageCard` and `OntologyStatusCard` draw (read; empty; unmeasured; a refused list; a retry in flight, the pressed one and the others pointing at the note; unavailable controls with their reasons; the forbidden note; a selected and an unselected row; a failed probe) passes `scanColourRoles` over the card as exactly that state's set of pairs, so a text drawn in another role (a failure pill in a neutral tone, a selected row's text in `fg-secondary`, which `info-tint` does not carry) fails.
|
|
450
|
+
- **Ingest tab and a run's step timeline:** every state `DocumentIngestConsole` (read rows of every kind with the restricted marks; an empty list with no source and no domain; a refused list; an invalid identifier; a reachable and an unreachable inspection; the restricted warning with the advanced settings open and labels added; a promotion in flight; the forbidden note) and `PipelineRunSteps` (steps of every status with their reports and meters; the report's detail open; a fan-out group with its alert and the records after it; the terminal step)
|
|
451
|
+
draw passes `scanColourRoles` over the console or the timeline as exactly that state's set of pairs (a neutral pill is `fg-secondary` on `sunken`).
|
|
322
452
|
- **Qdrant storage card's selected look:** the four ratios of the focus ring, the tint and the start-edge bar (see `QdrantStorageCard`), recomputed in both schemes.
|
|
323
453
|
|
|
324
|
-
|
|
325
|
-
Each marks its root element `data-cui-migration="pending"`, `tests/migratedSources.spec.ts` keeps it on its `NOT_YET_MIGRATED` list (which may only shrink, and which no other source may join), and the console's browser checks (axe, the visual contract) leave its subtree out. The colours they write as classes or inline values are not measured: they do not reach AAA, and each component moves onto the colour roles, where the role scan covers it, in the stage that redraws its tab.
|
|
454
|
+
Every component is drawn on the console foundation, so no element it renders carries `data-cui-migration="pending"` and the console's browser checks (axe, the visual contract) leave none of it out; `tests/migratedSources.spec.ts` checks that no source carries the mark and exempts no source from its paint, list and width checks (the width check allows only the history master minimum's declaration).
|
|
326
455
|
Colour pairs outside this section are not measured.
|
|
327
456
|
|
|
328
457
|
## Shared Presentation (`pipelinePresentation.ts`, `RecordedInstant.tsx`)
|
|
329
458
|
|
|
330
459
|
Exported for hosts that draw their own run tables, so a run reads the same everywhere.
|
|
331
460
|
|
|
332
|
-
- `ledgerStepLabel(key)` — the name of any step a run records: a
|
|
333
|
-
`documentKindLabel(kind)` names a run's document kind the same way (`DOCUMENT_KIND_LABELS`, or `知らない種類 (<kind>)`). `PipelineRunSteps` draws a step with an unknown key as one card, never inside a fan-out group. The flow diagram and the step metrics never show the terminal step (
|
|
461
|
+
- `ledgerStepLabel(key)` — the name of any step a run records: a released step's recorded label (`recordedStepOf`), the terminal step `record_document_state` as `RUN_TERMINAL_STEP_LABEL` (`文書の状態の記録`), and a key the current vocabulary does not know (an `UnknownTerm`) as `知らない段 (<key>)`.
|
|
462
|
+
`documentKindLabel(kind)` names a run's document kind the same way (`DOCUMENT_KIND_LABELS`, or `知らない種類 (<kind>)`). `PipelineRunSteps` draws a step with an unknown key as one card, never inside a fan-out group. The flow diagram and the step metrics never show the terminal step (no flow of the flow catalogue names it); only `PipelineRunSteps` does. `RUN_TERMINAL_STEP_OUTCOME_LABELS` words a succeeded terminal step's outcome.
|
|
334
463
|
- `documentHistoryPresentation` — the history's words for hosts: `FACET_LABELS`, `DOCUMENT_STATE_CAUSE_LABELS`, `facetCountsText(counts)`, `revisionLabel`, and the restricted marks' fixed texts `RESTRICTED_DOCUMENT_TEXT` (`隔離 (RESTRICTED) の文書`), `RESTRICTED_STATE_TEXT` and `RESTRICTED_RUN_TEXT` (`隔離されていた期間の実行`), so a host table that draws a mark row writes the same text.
|
|
335
464
|
|
|
336
465
|
- `syncStatusPresentation` — the words of every sync status (`SYNC_STATUS_LABELS`: `待機中`・`本文抽出中`・`AI 要約・構造化中`・`ベクトル/グラフ投影中`・`同期完了`, and a failure as `失敗: <status>`), the tone of its badge (`syncStatusTone`: a failure `danger`, `SYNCED` `success`, the rest `info`) and `NO_ENRICHMENT_RECORD_TEXT` (`取り込みの記録なし`), read by the recent-ingest table and the processing-objects list alike.
|
|
337
|
-
- `PIPELINE_RUN_STATUS_PRESENTATION` — `{ label
|
|
338
|
-
- `
|
|
466
|
+
- `PIPELINE_RUN_STATUS_PRESENTATION` — `{ label }` for each run status (`RUNNING` / `SUCCEEDED` / `FAILED` / `CANCELLED`) and for the two runs waiting for another attempt, `RUNNING_RETRY_SCHEDULED` (`再試行待ち`, a running run the executor keeps open until its next attempt continues it) and `FAILED_RETRY_SCHEDULED` (`失敗 (再試行予定)`); pick the key with `pipelineRunPresentationKey({ status, retryScheduled })` (a retry marker on a succeeded or cancelled run contradicts the record contract, `checkRunRetrySchedule` of `@aiquants/enrichment-core`, and is ignored).
|
|
467
|
+
- `PIPELINE_RUN_STATUS_TONES` — the `Pill` tone of each run badge drawn with the console foundation (the badge's words state the status, so colour is never the only cue).
|
|
468
|
+
- `PIPELINE_STEP_STATUS_PRESENTATION` — `{ label }` per step status.
|
|
469
|
+
- `PIPELINE_STEP_STATUS_TONES` — the `Pill` tone of each step status's badge drawn with the console foundation (`danger` for a failure, `info` for a step under way, `success`, and `neutral` for a skipped or cancelled step; the badge's words state the status, so no status is told by its tone alone).
|
|
339
470
|
- `PIPELINE_TRIGGER_LABELS` — operator-facing names of the triggers `PipelineTriggerSchema` allows: `ingest` (取り込み), `retry` (再試行), `resync` (再同期).
|
|
471
|
+
- `pipelineFlowLabel({ flowKey, flowVersion })` — the flow a run ran: `pdf 版 2`, or `pdf (版なし)` (`UNVERSIONED_FLOW_LABEL`) for a run recorded before flows had versions. The key is written as recorded (a built-in flow's key is its document kind, a child flow's its own, such as `child.read`); no table of flow names is copied here.
|
|
340
472
|
- `skipReasonLabel(text)` — reads the text through `parseSkipReason`: `gate_closed:<flag>` → `<flag> が無効`, `no_children` → `子アセットなし`, `source_skipped:<step>` → `<step label> が実行されなかった`, `nothing_to_read:<token>` → `NOTHING_TO_READ_LABELS[token]` (`no_pages` → `読む対象なし (ページなし)`, `no_pages_rendered` → `読む対象なし (描画できたページなし)`), and the terminal step's `superseded` → `実行の版が現在の版でなくなった`, `recording_disabled` → `記録の切替が無効`, `recording_misconfigured` → `記録の切替の設定の誤り`, and the unlock step's `not_protected` → `パスワードが付いていない`; text outside the grammar is returned verbatim.
|
|
341
473
|
A fan-out step's per-child records — including a child skipped as `gate_closed:ENABLE_OCR` or `nothing_to_read:no_pages` — are grouped under the step in the step timeline, in the tree order of their targets' tree paths, each with its own reason.
|
|
342
|
-
- `RecordedInstant({ iso, testId })` — a recorded instant in the root's zone, to the second (`2026/09/24 01:02:03`), in a
|
|
474
|
+
- `RecordedInstant({ iso, testId })` — a recorded instant in the root's zone, to the second (`2026/09/24 01:02:03`), in a `Measured` text that moves whole under `testId` (`data-measured="true"`, `data-cui-keep="whole"`, tabular figures, moved to the next line whole instead of breaking between the date and the time); `iso: null` is the root's unmeasured label in the same slot (`data-measured="false"`, `data-cui-keep="whole"`). A refused instant throws `ZonedTimeError` (see **Time zone is the root's**). Render it inside the Japanese console root, like every component that reads the root.
|
|
343
475
|
- `FinishedInstant({ iso, testId })` — a finish time that may still be open: `RecordedInstant` for a finished run or step, or the word `未完了` (`UNFINISHED_LABEL`) outside any slot while `iso` is `null`. It checks the root in both cases.
|
|
344
|
-
- The package publishes no function that
|
|
476
|
+
- The package publishes no function that writes a given instant as text: a host table draws its instants with these two, so they share the root's clock, the slot and its hook with every panel of the package.
|
|
477
|
+
- `recordedInstantContent(timeZone)` / `recordedInstantSampleText(timeZone)` — what a cell drawing one recorded instant holds for `@aiquants/console-ui/core`'s `fixedColumnPx` (a `measured` content) and the sample instant it holds, written by the same writer `RecordedInstant` uses (this package's `ja-JP`, in the given zone, the root's): a host that draws these slots in a table of its own sizes the column with them, so the column fits what it draws whatever the root's format locale.
|
|
345
478
|
|
|
346
479
|
## License
|
|
347
480
|
|