@patwoodman/dsh-sdlc-flow-ui 0.0.0-stage → 0.4.0-beta.14
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/LICENSE +21 -0
- package/README.md +38 -2
- package/cordis.patch.yml +11 -0
- package/icon.svg +28 -0
- package/lib/client/SdlcTab.css +38 -0
- package/lib/client/SdlcTab.jsx +73 -0
- package/lib/client/api.js +106 -0
- package/lib/client/definition.js +23 -0
- package/lib/client/index.js +49 -0
- package/lib/client/locales.js +68 -0
- package/lib/client/panels.jsx +263 -0
- package/lib/client/store.js +91 -0
- package/lib/client.js +682 -0
- package/lib/index.js +7 -0
- package/lib/routes.js +88 -0
- package/lib/server/handlers.js +186 -0
- package/lib/server/plugin.js +166 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +70 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 SDLC Orchestrator contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @patwoodman/dsh-sdlc-flow-ui — the SDLC web panel
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The browser half of the orchestrator: an **SDLC** tab in the web app's right
|
|
4
|
+
sidebar, plus the host routes that feed it.
|
|
5
|
+
|
|
6
|
+
| Panel | What it shows |
|
|
7
|
+
|---|---|
|
|
8
|
+
| Status | live project, task and spend summary from `sdlc.db` |
|
|
9
|
+
| Board | tasks grouped by state-machine state |
|
|
10
|
+
| Config | a validated editor for `.sdlc/config.yml` (writes are rejected when validation fails) |
|
|
11
|
+
| Review | side-by-side time/tokens/tracepaths for two runs of one task |
|
|
12
|
+
|
|
13
|
+
Host routes: `/api/sdlc.status`, `/api/sdlc.board`, `/api/sdlc.task`,
|
|
14
|
+
`/api/sdlc.compare`, `/api/sdlc.config` (GET + validated POST),
|
|
15
|
+
`/api/sdlc.metrics`.
|
|
16
|
+
|
|
17
|
+
`lib/client.js` is a prebuilt closure-factory bundle that registers into the
|
|
18
|
+
right-sidebar slot system; it needs no build step on the consumer's machine.
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
Installed into the interactive lead only — a `web`-platform profile. It is a
|
|
23
|
+
member of [`@patwoodman/dsh-sdlc-flow`](https://www.npmjs.com/package/@patwoodman/dsh-sdlc-flow):
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
dsh plugin --profile sdlc-web add @patwoodman/dsh-sdlc-flow
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
It must never be mounted in the headless worker profile (E6).
|
|
30
|
+
|
|
31
|
+
## Requirements
|
|
32
|
+
|
|
33
|
+
- `@deepseek-ai/dsh` `>=0.2.0-rc.2 <0.3.0-0`
|
|
34
|
+
- Node.js ≥ 22.5
|
|
35
|
+
- a profile whose client platform is `web`
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# sdlc-client-ui — SDLC orchestrator web UI (spec v3 C.1, optional).
|
|
2
|
+
#
|
|
3
|
+
# Installed into the sdlc-web profile ONLY (interactive lead). It registers
|
|
4
|
+
# `sdlc.*` host routes (read-only status/board/review + validated config writes)
|
|
5
|
+
# and contributes the browser `sdlc` sidebar tab (config editor, task board,
|
|
6
|
+
# side-by-side review). Never installed into sdlc-worker (E6: no core there,
|
|
7
|
+
# and no UI in a headless profile).
|
|
8
|
+
- insert:
|
|
9
|
+
- id: sdlc-ui
|
|
10
|
+
name: '@patwoodman/dsh-sdlc-flow-ui'
|
|
11
|
+
config: {}
|
package/icon.svg
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="Universal SDLC Orchestrator">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="sdlc-bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#1e3a8a"/>
|
|
5
|
+
<stop offset="1" stop-color="#0e7490"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
</defs>
|
|
8
|
+
<rect width="64" height="64" rx="14" fill="url(#sdlc-bg)"/>
|
|
9
|
+
<g fill="none" stroke="#e0f2fe" stroke-width="2.4" stroke-linecap="round">
|
|
10
|
+
<path d="M32 17v9"/>
|
|
11
|
+
<path d="M32 34v4"/>
|
|
12
|
+
<path d="M24 44l-8 6"/>
|
|
13
|
+
<path d="M40 44l8 6"/>
|
|
14
|
+
</g>
|
|
15
|
+
<g fill="#e0f2fe">
|
|
16
|
+
<circle cx="32" cy="13" r="4.6"/>
|
|
17
|
+
<circle cx="32" cy="29" r="4.6"/>
|
|
18
|
+
<circle cx="19" cy="47" r="4.6"/>
|
|
19
|
+
<circle cx="45" cy="47" r="4.6"/>
|
|
20
|
+
</g>
|
|
21
|
+
<g fill="#0e7490">
|
|
22
|
+
<circle cx="32" cy="13" r="1.7"/>
|
|
23
|
+
<circle cx="32" cy="29" r="1.7"/>
|
|
24
|
+
<circle cx="19" cy="47" r="1.7"/>
|
|
25
|
+
<circle cx="45" cy="47" r="1.7"/>
|
|
26
|
+
</g>
|
|
27
|
+
<path d="M27.5 39.5l4.5 4.5 4.5-4.5" fill="none" stroke="#67e8f9" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
28
|
+
</svg>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/* SDLC tab body: panel switcher + four panels. Plain CSS (no modules — the
|
|
2
|
+
zero-dependency build inlines this file's sibling CSS as text). */
|
|
3
|
+
.sdlc-root {
|
|
4
|
+
display: flex;
|
|
5
|
+
flex: 1 1 auto;
|
|
6
|
+
flex-direction: column;
|
|
7
|
+
height: 100%;
|
|
8
|
+
min-height: 0;
|
|
9
|
+
font-size: 13px;
|
|
10
|
+
line-height: 1.5;
|
|
11
|
+
overflow: auto;
|
|
12
|
+
padding: 8px 12px;
|
|
13
|
+
}
|
|
14
|
+
.sdlc-tabs {
|
|
15
|
+
display: flex;
|
|
16
|
+
gap: 4px;
|
|
17
|
+
margin-bottom: 8px;
|
|
18
|
+
}
|
|
19
|
+
.sdlc-tabs button[aria-selected="true"] {
|
|
20
|
+
font-weight: 600;
|
|
21
|
+
text-decoration: underline;
|
|
22
|
+
}
|
|
23
|
+
.sdlc-root table {
|
|
24
|
+
border-collapse: collapse;
|
|
25
|
+
margin: 8px 0;
|
|
26
|
+
}
|
|
27
|
+
.sdlc-root th,
|
|
28
|
+
.sdlc-root td {
|
|
29
|
+
border: 1px solid #8886;
|
|
30
|
+
padding: 2px 8px;
|
|
31
|
+
text-align: left;
|
|
32
|
+
}
|
|
33
|
+
.sdlc-root code {
|
|
34
|
+
font-size: 12px;
|
|
35
|
+
}
|
|
36
|
+
.sdlc-root ul {
|
|
37
|
+
padding-left: 20px;
|
|
38
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/sdlc-client-ui/src/client/SdlcTab.jsx — the SDLC sidebar tab body.
|
|
3
|
+
*
|
|
4
|
+
* Panel switcher (Status | Board | Config | Review) over the four stores.
|
|
5
|
+
* Uses `useSyncExternalStore` against the framework-free stores so the tab
|
|
6
|
+
* works under any React 18 the host provides.
|
|
7
|
+
*
|
|
8
|
+
* NOTE (subset rule): prop callbacks are single expressions — the offline
|
|
9
|
+
* JSX compiler supports balanced braces but not statement blocks or bare
|
|
10
|
+
* arrow parameters inside `{…}` props. Multi-statement handlers live as
|
|
11
|
+
* named functions above; panel switching loads lazily via the tab buttons'
|
|
12
|
+
* onClick below (single-expression arrows are fine).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { useState, useSyncExternalStore } from 'react';
|
|
16
|
+
import { StatusPanel, BoardPanel, ConfigPanel, ReviewPanel } from './panels.jsx';
|
|
17
|
+
|
|
18
|
+
const PANELS = ['status', 'board', 'config', 'review'];
|
|
19
|
+
|
|
20
|
+
export function SdlcTab({ stores, t }) {
|
|
21
|
+
const [panel, setPanel] = useState('status');
|
|
22
|
+
const status = useSyncExternalStore(stores.status.subscribe, stores.status.getSnapshot);
|
|
23
|
+
const board = useSyncExternalStore(stores.board.subscribe, stores.board.getSnapshot);
|
|
24
|
+
const config = useSyncExternalStore(stores.config.subscribe, stores.config.getSnapshot);
|
|
25
|
+
const review = useSyncExternalStore(stores.review.subscribe, stores.review.getSnapshot);
|
|
26
|
+
|
|
27
|
+
const selectPanel = (p) => setPanel(p);
|
|
28
|
+
const openTaskReview = (taskId) => {
|
|
29
|
+
void taskId;
|
|
30
|
+
setPanel('review');
|
|
31
|
+
};
|
|
32
|
+
const reloadStatus = () => stores.loadStatus();
|
|
33
|
+
const reloadBoard = () => stores.loadBoard();
|
|
34
|
+
const reloadConfig = () => stores.loadConfig();
|
|
35
|
+
const saveConfig = (path, value) => stores.saveConfig(path, value);
|
|
36
|
+
const loadCompare = (task, a, b) => stores.loadCompare(task, a, b);
|
|
37
|
+
|
|
38
|
+
return (
|
|
39
|
+
<div className="sdlc-root">
|
|
40
|
+
<div className="sdlc-tabs" role="tablist" aria-label="SDLC panels">
|
|
41
|
+
{PANELS.map((p) => (
|
|
42
|
+
<button
|
|
43
|
+
key={p}
|
|
44
|
+
role="tab"
|
|
45
|
+
type="button"
|
|
46
|
+
aria-selected={panel === p}
|
|
47
|
+
onClick={selectPanel}
|
|
48
|
+
>
|
|
49
|
+
{t(`panel.${p}`)}
|
|
50
|
+
</button>
|
|
51
|
+
))}
|
|
52
|
+
</div>
|
|
53
|
+
{panel === 'status' && <StatusPanel snapshot={status} onReload={reloadStatus} />}
|
|
54
|
+
{panel === 'board' && (
|
|
55
|
+
<BoardPanel
|
|
56
|
+
snapshot={board}
|
|
57
|
+
onReload={reloadBoard}
|
|
58
|
+
onOpenTask={openTaskReview}
|
|
59
|
+
/>
|
|
60
|
+
)}
|
|
61
|
+
{panel === 'config' && (
|
|
62
|
+
<ConfigPanel snapshot={config} onReload={reloadConfig} onSave={saveConfig} />
|
|
63
|
+
)}
|
|
64
|
+
{panel === 'review' && <ReviewPanel snapshot={review} onCompare={loadCompare} />}
|
|
65
|
+
</div>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function SdlcTitle({ t }) {
|
|
70
|
+
return <span>{t('tab.label')}</span>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export default { SdlcTab, SdlcTitle };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/sdlc-client-ui/src/client/api.js — browser fetch face over the `sdlc.*` routes.
|
|
3
|
+
*
|
|
4
|
+
* Zero dependencies, no framework: thin JSON helpers with the same
|
|
5
|
+
* never-fabricate rule as the eval collectors — an unmeasurable endpoint
|
|
6
|
+
* resolves to `{ available: false, reason }`, never to a zero.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
SDLC_STATUS_ROUTE, SDLC_BOARD_ROUTE, SDLC_TASK_ROUTE, SDLC_COMPARE_ROUTE,
|
|
11
|
+
SDLC_CONFIG_ROUTE, SDLC_METRICS_ROUTE,
|
|
12
|
+
isStatusPayload, isBoardPayload, isConfigPayload,
|
|
13
|
+
} from '../routes.js';
|
|
14
|
+
|
|
15
|
+
async function getJson(path) {
|
|
16
|
+
let res;
|
|
17
|
+
try {
|
|
18
|
+
res = await fetch(`/${path}`, { headers: { accept: 'application/json' } });
|
|
19
|
+
} catch (err) {
|
|
20
|
+
return { available: false, reason: `network: ${err?.message ?? err}` };
|
|
21
|
+
}
|
|
22
|
+
if (!res.ok) {
|
|
23
|
+
let detail = '';
|
|
24
|
+
try { detail = JSON.stringify(await res.json()); } catch { detail = res.statusText; }
|
|
25
|
+
return { available: false, reason: `http ${res.status}: ${detail.slice(0, 200)}` };
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return { available: true, data: await res.json() };
|
|
29
|
+
} catch {
|
|
30
|
+
return { available: false, reason: 'invalid JSON from host' };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function postJson(path, body) {
|
|
35
|
+
let res;
|
|
36
|
+
try {
|
|
37
|
+
res = await fetch(`/${path}`, {
|
|
38
|
+
method: 'POST',
|
|
39
|
+
headers: { 'content-type': 'application/json', accept: 'application/json' },
|
|
40
|
+
body: JSON.stringify(body),
|
|
41
|
+
});
|
|
42
|
+
} catch (err) {
|
|
43
|
+
return { available: false, reason: `network: ${err?.message ?? err}` };
|
|
44
|
+
}
|
|
45
|
+
let data = null;
|
|
46
|
+
try { data = await res.json(); } catch { data = null; }
|
|
47
|
+
if (!res.ok || !data) {
|
|
48
|
+
return { available: false, reason: `http ${res.status}: ${JSON.stringify(data)?.slice(0, 200) ?? res.statusText}` };
|
|
49
|
+
}
|
|
50
|
+
return { available: true, data };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export async function fetchStatus() {
|
|
54
|
+
const res = await getJson(SDLC_STATUS_ROUTE);
|
|
55
|
+
if (!res.available) return res;
|
|
56
|
+
if (!isStatusPayload(res.data)) return { available: false, reason: 'host returned a malformed status payload' };
|
|
57
|
+
return res;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function fetchBoard() {
|
|
61
|
+
const res = await getJson(SDLC_BOARD_ROUTE);
|
|
62
|
+
if (!res.available) return res;
|
|
63
|
+
if (!isBoardPayload(res.data)) return { available: false, reason: 'host returned a malformed board payload' };
|
|
64
|
+
return res;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export async function fetchTask(id) {
|
|
68
|
+
return getJson(`${SDLC_TASK_ROUTE}?id=${encodeURIComponent(id)}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function fetchCompare(task, a, b) {
|
|
72
|
+
const q = `task=${encodeURIComponent(task)}&a=${encodeURIComponent(a)}&b=${encodeURIComponent(b)}`;
|
|
73
|
+
return getJson(`${SDLC_COMPARE_ROUTE}?${q}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function fetchConfig() {
|
|
77
|
+
const res = await getJson(SDLC_CONFIG_ROUTE);
|
|
78
|
+
if (!res.available) return res;
|
|
79
|
+
if (!isConfigPayload(res.data)) return { available: false, reason: 'host returned a malformed config payload' };
|
|
80
|
+
return res;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function writeConfig(path, value) {
|
|
84
|
+
return postJson(SDLC_CONFIG_ROUTE, { path, value });
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function fetchMetrics() {
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(`/${SDLC_METRICS_ROUTE}`, { headers: { accept: 'text/plain' } });
|
|
90
|
+
if (!res.ok) return { available: false, reason: `http ${res.status}` };
|
|
91
|
+
return { available: true, data: await res.text() };
|
|
92
|
+
} catch (err) {
|
|
93
|
+
return { available: false, reason: `network: ${err?.message ?? err}` };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The single api face the client consumes. NAMED on purpose: the closure-factory
|
|
99
|
+
* bundle concatenates the client sources into one scope, where a default import
|
|
100
|
+
* of this module would be stripped UNBOUND (`api is not defined` at client
|
|
101
|
+
* apply — shipped broken until the entry contract test called apply). The
|
|
102
|
+
* default export stays for source-level ergonomics; the bundler strips it.
|
|
103
|
+
*/
|
|
104
|
+
export const api = { fetchStatus, fetchBoard, fetchTask, fetchCompare, fetchConfig, writeConfig, fetchMetrics };
|
|
105
|
+
|
|
106
|
+
export default api;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/sdlc-client-ui/src/client/definition.js — what the `sdlc` tab type IS.
|
|
3
|
+
*
|
|
4
|
+
* A right-sidebar page (the files-tab pattern): it claims the
|
|
5
|
+
* `dsh-resource://sdlc-review/task/…` addresses the board mints for per-task
|
|
6
|
+
* review, and offers the SDLC entry as a guide box otherwise.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const SDLC_KIND = 'sdlc';
|
|
10
|
+
|
|
11
|
+
/** This implementation's identity in the tab system, and the key its body registers under. */
|
|
12
|
+
export const SDLC_ID = '@patwoodman/dsh-sdlc-flow-ui';
|
|
13
|
+
|
|
14
|
+
export function sdlcDefinition(t) {
|
|
15
|
+
return {
|
|
16
|
+
id: SDLC_ID,
|
|
17
|
+
kind: SDLC_KIND,
|
|
18
|
+
priority: 'builtin',
|
|
19
|
+
title: () => t('tab.title'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export default { SDLC_KIND, SDLC_ID, sdlcDefinition };
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/sdlc-client-ui/src/client/index.js — browser half of the SDLC web UI.
|
|
3
|
+
*
|
|
4
|
+
* Registers the `sdlc` right-sidebar tab type + body + title (the files-tab
|
|
5
|
+
* pattern): the type into `ctx.sidebarRightTabs`, the body into the keyed
|
|
6
|
+
* `sidebar.right.pane.tab` seat and the title into
|
|
7
|
+
* `sidebar.right.pane.tab.title`, both under the type's id.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { api } from './api.js';
|
|
11
|
+
import { createSdlcStores } from './store.js';
|
|
12
|
+
import { NS, en, zh } from './locales.js';
|
|
13
|
+
import { SDLC_ID, sdlcDefinition } from './definition.js';
|
|
14
|
+
import { SdlcTab, SdlcTitle } from './SdlcTab.jsx';
|
|
15
|
+
|
|
16
|
+
/** Required browser services: tab registry, keyed seats, Remote, copy. */
|
|
17
|
+
export const inject = ['slots', 'locale', 'sidebarRightTabs', 'sidebarRight', 'remote'];
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Client plugin body: register dictionaries, the tab type, and the tab body.
|
|
21
|
+
* @param ctx - client root context.
|
|
22
|
+
*/
|
|
23
|
+
export function apply(ctx) {
|
|
24
|
+
const t = ctx.locale.bind(NS);
|
|
25
|
+
ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'sdlc-ui: dictionaries');
|
|
26
|
+
ctx.effect(() => ctx.sidebarRightTabs.register(sdlcDefinition(t)), 'sdlc-ui: sdlc tab type');
|
|
27
|
+
|
|
28
|
+
const stores = createSdlcStores(api);
|
|
29
|
+
ctx.effect(() => () => stores.dispose(), 'sdlc-ui: dispose');
|
|
30
|
+
ctx.on('connection/reset', () => stores.reset());
|
|
31
|
+
|
|
32
|
+
ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab', () => ctx.slots.register(
|
|
33
|
+
{
|
|
34
|
+
name: 'sidebar.right.pane.tab', key: SDLC_ID, locale: NS, store: stores,
|
|
35
|
+
inject: () => ({ stores, t }),
|
|
36
|
+
},
|
|
37
|
+
SdlcTab,
|
|
38
|
+
)), 'sdlc-ui: sdlc tab body');
|
|
39
|
+
ctx.effect(() => ctx.slots.inject('sidebar.right.pane.tab.title', () => ctx.slots.register(
|
|
40
|
+
{ name: 'sidebar.right.pane.tab.title', key: SDLC_ID },
|
|
41
|
+
(props) => SdlcTitle({ ...props, t }),
|
|
42
|
+
)), 'sdlc-ui: sdlc tab title');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The closure bundle strips this line and re-attaches the same object as
|
|
46
|
+
// `exports.default` (see scripts/build-ui.mjs). The web loader's unwrapExports
|
|
47
|
+
// prefers `.default`; the object — not the bare apply function — is what keeps
|
|
48
|
+
// `inject` visible, so the sidebar tab actually receives slots/locale/remote.
|
|
49
|
+
export default { apply, inject };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* packages/sdlc-client-ui/src/client/locales.js — `sdlc` namespace dictionaries.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Dictionary namespace owned by this plugin. */
|
|
6
|
+
export const NS = 'sdlc';
|
|
7
|
+
|
|
8
|
+
/** English dictionary (the key-set source of truth). */
|
|
9
|
+
export const en = {
|
|
10
|
+
'tab.label': 'SDLC',
|
|
11
|
+
'tab.title': 'DSH SDLC Flow',
|
|
12
|
+
'status.loading': 'Loading flow status…',
|
|
13
|
+
'status.missing': 'Status unavailable: {reason}',
|
|
14
|
+
'status.empty': 'No project initialised — run `sdlc init` first.',
|
|
15
|
+
'status.tasks': '{count} tasks',
|
|
16
|
+
'status.spend': '${cost} (estimate) · {tokens} tokens',
|
|
17
|
+
'board.loading': 'Loading task board…',
|
|
18
|
+
'board.missing': 'Board unavailable: {reason}',
|
|
19
|
+
'board.empty': 'No tasks yet.',
|
|
20
|
+
'config.loading': 'Loading configuration…',
|
|
21
|
+
'config.missing': 'Configuration unavailable: {reason}',
|
|
22
|
+
'config.save': 'Save',
|
|
23
|
+
'config.saving': 'Saving…',
|
|
24
|
+
'config.saved': 'Saved.',
|
|
25
|
+
'config.invalid': 'Rejected: {reason}',
|
|
26
|
+
'config.validationErrors': '{count} error(s)',
|
|
27
|
+
'review.pickRuns': 'Pick two runs of a task to compare.',
|
|
28
|
+
'review.loading': 'Loading comparison…',
|
|
29
|
+
'review.missing': 'Comparison unavailable: {reason}',
|
|
30
|
+
'review.compare': 'Compare',
|
|
31
|
+
'review.traceTitle': 'Trace path',
|
|
32
|
+
'panel.status': 'Status',
|
|
33
|
+
'panel.board': 'Board',
|
|
34
|
+
'panel.config': 'Config',
|
|
35
|
+
'panel.review': 'Review',
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/** Simplified Chinese dictionary. */
|
|
39
|
+
export const zh = {
|
|
40
|
+
'tab.label': 'SDLC',
|
|
41
|
+
'tab.title': 'SDLC 编排器',
|
|
42
|
+
'status.loading': '正在加载编排器状态…',
|
|
43
|
+
'status.missing': '状态不可用:{reason}',
|
|
44
|
+
'status.empty': '尚未初始化项目 — 请先运行 `sdlc init`。',
|
|
45
|
+
'status.tasks': '{count} 个任务',
|
|
46
|
+
'status.spend': '${cost}(估算)· {tokens} tokens',
|
|
47
|
+
'board.loading': '正在加载任务看板…',
|
|
48
|
+
'board.missing': '看板不可用:{reason}',
|
|
49
|
+
'board.empty': '暂无任务。',
|
|
50
|
+
'config.loading': '正在加载配置…',
|
|
51
|
+
'config.missing': '配置不可用:{reason}',
|
|
52
|
+
'config.save': '保存',
|
|
53
|
+
'config.saving': '保存中…',
|
|
54
|
+
'config.saved': '已保存。',
|
|
55
|
+
'config.invalid': '已拒绝:{reason}',
|
|
56
|
+
'config.validationErrors': '{count} 个错误',
|
|
57
|
+
'review.pickRuns': '选择同一任务的两次运行进行对比。',
|
|
58
|
+
'review.loading': '正在加载对比…',
|
|
59
|
+
'review.missing': '对比不可用:{reason}',
|
|
60
|
+
'review.compare': '对比',
|
|
61
|
+
'review.traceTitle': '追踪路径',
|
|
62
|
+
'panel.status': '状态',
|
|
63
|
+
'panel.board': '看板',
|
|
64
|
+
'panel.config': '配置',
|
|
65
|
+
'panel.review': '复核',
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
export default { NS, en, zh };
|