@work-graph/cli 0.2.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 +31 -0
- package/bin/work-graph.mjs +238 -0
- package/package.json +38 -0
- package/vendor/packages/design-tokens/generated/gripe-dark-default.css +67 -0
- package/vendor/packages/design-tokens/generated/marketplace-default.css +67 -0
- package/vendor/packages/design-tokens/generated/workgraph-dark.css +67 -0
- package/vendor/packages/workgraph-mcp/README.md +28 -0
- package/vendor/packages/workgraph-mcp/bin/workgraph-mcp.mjs +21 -0
- package/vendor/packages/workgraph-mcp/package.json +37 -0
- package/vendor/packages/workgraph-mcp/src/handlers.mjs +761 -0
- package/vendor/packages/workgraph-mcp/src/index.mjs +638 -0
- package/vendor/packages/workgraph-mcp/src/prompts.mjs +162 -0
- package/vendor/public/assets/workgraph-logo.svg +11 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Medium.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Regular.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/GraphikLCG-Semibold.woff2 +0 -0
- package/vendor/public/fonts/GraphikLCG/stylesheet.css +25 -0
- package/vendor/public/graph-canvas-lit-flow.css +154 -0
- package/vendor/public/graph-canvas-lit-flow.css.map +7 -0
- package/vendor/public/graph-canvas-lit-flow.js +8530 -0
- package/vendor/public/graph-canvas-lit-flow.js.map +7 -0
- package/vendor/src/agentBehaviorRulesAudit.mjs +168 -0
- package/vendor/src/agentBehaviorRulesBundle.mjs +144 -0
- package/vendor/src/agentRunApi.mjs +136 -0
- package/vendor/src/agentToolLoopGuard.mjs +88 -0
- package/vendor/src/agentWorkerClaudeProvider.mjs +288 -0
- package/vendor/src/agentWorkerCursorSdkProvider.mjs +156 -0
- package/vendor/src/agentWorkerLiveLoop.mjs +455 -0
- package/vendor/src/agentWorkerLocalCliProvider.mjs +217 -0
- package/vendor/src/agentWorkerLocalRunner.mjs +246 -0
- package/vendor/src/agentWorkerOpenAiProvider.mjs +459 -0
- package/vendor/src/analyticsPanelProjection.mjs +212 -0
- package/vendor/src/analyticsRecordStore.mjs +165 -0
- package/vendor/src/analyticsRecordWorkItems.mjs +104 -0
- package/vendor/src/architectureL1Canon.mjs +419 -0
- package/vendor/src/architectureLayout.mjs +229 -0
- package/vendor/src/architectureSnapshot.mjs +490 -0
- package/vendor/src/architectureViewsProjection.mjs +116 -0
- package/vendor/src/atomInspector.mjs +253 -0
- package/vendor/src/atomInspectorApi.mjs +130 -0
- package/vendor/src/auditGapMatrixRefresh.mjs +121 -0
- package/vendor/src/backlogSchemaLint.mjs +176 -0
- package/vendor/src/blockedOnebaseGoPreflightEval.mjs +100 -0
- package/vendor/src/bracketIrTraceSignal.mjs +93 -0
- package/vendor/src/bvcAtomParser.mjs +210 -0
- package/vendor/src/bvcDialectRegistry.mjs +86 -0
- package/vendor/src/bvcFileFormat.mjs +218 -0
- package/vendor/src/bvcFormatCli.mjs +55 -0
- package/vendor/src/bvcLintCli.mjs +48 -0
- package/vendor/src/bvcNewWritePolicy.mjs +70 -0
- package/vendor/src/charterPreflightPromoteGate.mjs +194 -0
- package/vendor/src/claimNoEligibleEval.mjs +205 -0
- package/vendor/src/closingAnalysisSuggest.mjs +59 -0
- package/vendor/src/codeGapAnalyzer.mjs +308 -0
- package/vendor/src/codeGapBacklogFeeder.mjs +82 -0
- package/vendor/src/codeGapDraftIntakeApi.mjs +307 -0
- package/vendor/src/codeGapOperatorProjection.mjs +60 -0
- package/vendor/src/codeSyntaxHighlight.mjs +123 -0
- package/vendor/src/codegenEvidence.mjs +187 -0
- package/vendor/src/compilerRoundTripCli.mjs +164 -0
- package/vendor/src/dagreGraphLayout.mjs +78 -0
- package/vendor/src/draftIntakePromotionRules.mjs +205 -0
- package/vendor/src/epicWorkScope.mjs +85 -0
- package/vendor/src/evalLiveLlmEnv.mjs +63 -0
- package/vendor/src/evidenceReadModel.mjs +167 -0
- package/vendor/src/gfsOverlayProjectPassport.mjs +235 -0
- package/vendor/src/globalStepPathToBvcReferences.mjs +196 -0
- package/vendor/src/goldenPath.mjs +69 -0
- package/vendor/src/graphCanvasLayout.mjs +464 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasMinimap.ts +261 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasSvgEdges.ts +259 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCanvasTheme.css +152 -0
- package/vendor/src/graphCanvasLitFlow/client/graphCardNode.ts +328 -0
- package/vendor/src/graphCanvasLitFlow/client/mountGraphCanvasLitFlow.ts +322 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeLabels.mjs +58 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasEdgeRouter.mjs +142 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasLayoutProfile.mjs +32 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasNodeMetrics.mjs +45 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjection.mjs +115 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasProjectionToFlow.mjs +133 -0
- package/vendor/src/graphCanvasLitFlow/graphCanvasTraversal.mjs +77 -0
- package/vendor/src/graphCanvasLitFlow/layoutIntentRoadmapWorkStack.mjs +73 -0
- package/vendor/src/graphCanvasLitFlow/resolveGraphCanvasOverlaps.mjs +77 -0
- package/vendor/src/graphRagContextSlice.mjs +461 -0
- package/vendor/src/gvmVerifyWorkerGate.mjs +95 -0
- package/vendor/src/homeSnapshotApi.mjs +131 -0
- package/vendor/src/homeSnapshotProjection.mjs +275 -0
- package/vendor/src/inboxEventStream.mjs +140 -0
- package/vendor/src/intentComposerApi.mjs +245 -0
- package/vendor/src/intentGraphGbcSliceBoundary.mjs +258 -0
- package/vendor/src/intentGraphProjection.mjs +208 -0
- package/vendor/src/intentHierarchy.mjs +241 -0
- package/vendor/src/intentNodeLint.mjs +107 -0
- package/vendor/src/intentNodeRuntime.mjs +185 -0
- package/vendor/src/intentRoadmapCanvas.mjs +393 -0
- package/vendor/src/intentRoadmapEpicProjection.mjs +122 -0
- package/vendor/src/intentRoadmapMermaid.mjs +165 -0
- package/vendor/src/intentRoadmapProjection.mjs +85 -0
- package/vendor/src/intentTreeLint.mjs +114 -0
- package/vendor/src/intentTreeMigration.mjs +150 -0
- package/vendor/src/intentTreeWorkItems.mjs +227 -0
- package/vendor/src/kanbanBoardProjection.mjs +58 -0
- package/vendor/src/languageAdapterRegistry.mjs +180 -0
- package/vendor/src/languageAdapters/goAdapter.mjs +62 -0
- package/vendor/src/languageAdapters/jsTsAdapter.mjs +60 -0
- package/vendor/src/languageAdapters/jsonYamlAdapter.mjs +103 -0
- package/vendor/src/languageAdapters/onebaseOsAdapter.mjs +55 -0
- package/vendor/src/languageAdapters/plaintextAdapter.mjs +36 -0
- package/vendor/src/languageAdapters/shared.mjs +68 -0
- package/vendor/src/languageAdapters/stepAdapter.mjs +81 -0
- package/vendor/src/lintPlanWorkAlignment.mjs +136 -0
- package/vendor/src/loopHintRepeatToolEval.mjs +153 -0
- package/vendor/src/lowcodeScaffoldCli.mjs +386 -0
- package/vendor/src/markdownDocumentRender.mjs +208 -0
- package/vendor/src/memoryPanelProjection.mjs +116 -0
- package/vendor/src/memoryRecordWriter.mjs +243 -0
- package/vendor/src/memoryWorkerSlice.mjs +238 -0
- package/vendor/src/migrateStepToBvc.mjs +133 -0
- package/vendor/src/missionControlServerHandlers.mjs +195 -0
- package/vendor/src/missionControlUiClient.mjs +278 -0
- package/vendor/src/onebaseCliCapabilityProbe.mjs +107 -0
- package/vendor/src/onebaseCliRunner.mjs +145 -0
- package/vendor/src/onebaseGrossProfitStaticVerify.mjs +98 -0
- package/vendor/src/onebaseParityEvidenceSync.mjs +88 -0
- package/vendor/src/onebasePvrgGraphNodes.mjs +257 -0
- package/vendor/src/onebaseRestEvidenceAdapter.mjs +216 -0
- package/vendor/src/onebaseVectorDslCodegenReadiness.mjs +137 -0
- package/vendor/src/onebaseWorkItemTemplate.mjs +154 -0
- package/vendor/src/onebaseWorkerTools.mjs +586 -0
- package/vendor/src/operatorShellProjection.mjs +102 -0
- package/vendor/src/pipelineProseRender.mjs +180 -0
- package/vendor/src/pipelineStageLint.mjs +118 -0
- package/vendor/src/promptRulesEditorApi.mjs +174 -0
- package/vendor/src/promptRulesProjection.mjs +134 -0
- package/vendor/src/pvrg/bladeAdapter.mjs +40 -0
- package/vendor/src/pvrgTaskScope.mjs +152 -0
- package/vendor/src/releaseGateMatrix.mjs +188 -0
- package/vendor/src/schematicView.mjs +305 -0
- package/vendor/src/seedAnalyticsRecord.mjs +217 -0
- package/vendor/src/semanticSearchBm25.mjs +103 -0
- package/vendor/src/semanticSearchExcerpts.mjs +68 -0
- package/vendor/src/semanticSearchTfidfVector.mjs +86 -0
- package/vendor/src/semanticSearchWorkflow.mjs +366 -0
- package/vendor/src/stepAtomFormatter.mjs +413 -0
- package/vendor/src/stepGraphSlice.mjs +318 -0
- package/vendor/src/ui/atoms/badge.mjs +40 -0
- package/vendor/src/ui/atoms/badgeClient.mjs +32 -0
- package/vendor/src/ui/atoms/button.mjs +114 -0
- package/vendor/src/ui/atoms/buttonClient.mjs +49 -0
- package/vendor/src/ui/atoms/icon.mjs +23 -0
- package/vendor/src/ui/atoms/input.mjs +38 -0
- package/vendor/src/ui/atoms/modal.mjs +44 -0
- package/vendor/src/ui/atoms/select.mjs +98 -0
- package/vendor/src/ui/backlogShellButtons.mjs +238 -0
- package/vendor/src/ui/htmlEscape.mjs +11 -0
- package/vendor/src/ui/molecules/rating.mjs +48 -0
- package/vendor/src/ui/molecules/tabs.mjs +70 -0
- package/vendor/src/ui/organisms/modal.mjs +1 -0
- package/vendor/src/ui/pages/uiKitPage.mjs +147 -0
- package/vendor/src/ui/workItemStatusTone.mjs +36 -0
- package/vendor/src/unifiedLinkageProjection.mjs +264 -0
- package/vendor/src/verificationLoop.mjs +206 -0
- package/vendor/src/workGraphBacklogPersist.mjs +234 -0
- package/vendor/src/workGraphBacklogUiServer.mjs +9192 -0
- package/vendor/src/workGraphBoundedTargetFileRead.mjs +178 -0
- package/vendor/src/workGraphCycleSlice.mjs +184 -0
- package/vendor/src/workGraphDaemonTick.mjs +307 -0
- package/vendor/src/workGraphDaemonWatch.mjs +157 -0
- package/vendor/src/workGraphEngineRoot.mjs +136 -0
- package/vendor/src/workGraphInstallLayout.mjs +65 -0
- package/vendor/src/workGraphLlmUsefulnessEval.mjs +611 -0
- package/vendor/src/workGraphPhasePromoteReadyQueue.mjs +159 -0
- package/vendor/src/workGraphProjectHost.mjs +149 -0
- package/vendor/src/workGraphProjectInit.mjs +392 -0
- package/vendor/src/workGraphPromoteReadyApi.mjs +115 -0
- package/vendor/src/workGraphRecoveryPolicy.mjs +124 -0
- package/vendor/src/workGraphRunnerQueueProjection.mjs +187 -0
- package/vendor/src/workGraphRuntime.mjs +1008 -0
- package/vendor/src/workGraphToolSurfaceAudit.mjs +372 -0
- package/vendor/src/workGraphToolTransportRuntime.mjs +195 -0
- package/vendor/src/workGraphWorkerProvider.mjs +600 -0
- package/vendor/src/workItemBvcQuality.mjs +262 -0
- package/vendor/src/workItemCreateAnalysis.mjs +157 -0
- package/vendor/src/workItemDecisionPipeline.mjs +278 -0
- package/vendor/src/workItemEpicCascade.mjs +176 -0
- package/vendor/src/workItemExecutionGate.mjs +78 -0
- package/vendor/src/workItemHierarchy.mjs +226 -0
- package/vendor/src/workItemProseLint.mjs +133 -0
- package/vendor/src/workItemTextRusify.mjs +794 -0
- package/vendor/src/workItemTraceEnvelope.mjs +158 -0
- package/vendor/src/workItemUiReferences.mjs +272 -0
- package/vendor/src/workflowEpicGrouping.mjs +67 -0
- package/vendor/src/workflowTreeProjection.mjs +53 -0
- package/vendor/src/workspaceRegistry.mjs +150 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { escapeHtml, escapeHtmlAttr } from '../htmlEscape.mjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {{ value?: string, label?: string, selected?: boolean }} option
|
|
5
|
+
*/
|
|
6
|
+
function renderOption(option) {
|
|
7
|
+
if (typeof option === 'string') {
|
|
8
|
+
return `<option value="${escapeHtmlAttr(option)}">${escapeHtml(option)}</option>`;
|
|
9
|
+
}
|
|
10
|
+
const value = option.value ?? '';
|
|
11
|
+
const label = option.label ?? value;
|
|
12
|
+
const selected = option.selected ? ' selected' : '';
|
|
13
|
+
return `<option value="${escapeHtmlAttr(value)}"${selected}>${escapeHtml(label)}</option>`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {{
|
|
18
|
+
* id?: string,
|
|
19
|
+
* name?: string,
|
|
20
|
+
* value?: string,
|
|
21
|
+
* options?: Array<string | { value?: string, label?: string, selected?: boolean }>,
|
|
22
|
+
* disabled?: boolean,
|
|
23
|
+
* hidden?: boolean,
|
|
24
|
+
* testId?: string,
|
|
25
|
+
* ariaLabel?: string,
|
|
26
|
+
* className?: string,
|
|
27
|
+
* }} props
|
|
28
|
+
*/
|
|
29
|
+
export function renderUiSelect(props = {}) {
|
|
30
|
+
const options = props.options ?? [];
|
|
31
|
+
const optionsHtml = options.map((option) => {
|
|
32
|
+
if (typeof option === 'object' && option.value != null && props.value === option.value) {
|
|
33
|
+
return renderOption({ ...option, selected: true });
|
|
34
|
+
}
|
|
35
|
+
return renderOption(option);
|
|
36
|
+
}).join('');
|
|
37
|
+
|
|
38
|
+
const classes = ['wg-select', props.className].filter(Boolean).join(' ');
|
|
39
|
+
const attrs = [
|
|
40
|
+
`class="${escapeHtmlAttr(classes)}"`,
|
|
41
|
+
props.id ? `id="${escapeHtmlAttr(props.id)}"` : '',
|
|
42
|
+
props.name ? `name="${escapeHtmlAttr(props.name)}"` : '',
|
|
43
|
+
props.disabled ? 'disabled' : '',
|
|
44
|
+
props.hidden ? 'hidden' : '',
|
|
45
|
+
props.testId ? `data-testid="${escapeHtmlAttr(props.testId)}"` : '',
|
|
46
|
+
props.ariaLabel ? `aria-label="${escapeHtmlAttr(props.ariaLabel)}"` : '',
|
|
47
|
+
].filter(Boolean).join(' ');
|
|
48
|
+
|
|
49
|
+
return `<select ${attrs}>${optionsHtml}</select>`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const UI_SELECT_CHEVRON_LIGHT = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%235e6c84' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E\")";
|
|
53
|
+
export const UI_SELECT_CHEVRON_DARK = "url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%239d9d9d' d='M2.5 4.5 6 8l3.5-3.5'/%3E%3C/svg%3E\")";
|
|
54
|
+
|
|
55
|
+
export const UI_SELECT_CSS = `
|
|
56
|
+
.wg-select {
|
|
57
|
+
box-sizing: border-box;
|
|
58
|
+
border: 1px solid var(--border);
|
|
59
|
+
border-radius: var(--ui-radius-control-sm, 0.25rem);
|
|
60
|
+
background-color: var(--panel-2);
|
|
61
|
+
color: var(--text);
|
|
62
|
+
padding: 8px 28px 8px 10px;
|
|
63
|
+
font: inherit;
|
|
64
|
+
font-size: 14px;
|
|
65
|
+
line-height: 1.2;
|
|
66
|
+
appearance: none;
|
|
67
|
+
-webkit-appearance: none;
|
|
68
|
+
color-scheme: light;
|
|
69
|
+
background-image: ${UI_SELECT_CHEVRON_LIGHT};
|
|
70
|
+
background-repeat: no-repeat;
|
|
71
|
+
background-size: 12px 12px;
|
|
72
|
+
background-position: right 8px center;
|
|
73
|
+
cursor: pointer;
|
|
74
|
+
}
|
|
75
|
+
.wg-select:hover {
|
|
76
|
+
background-color: var(--panel);
|
|
77
|
+
}
|
|
78
|
+
.wg-select:focus {
|
|
79
|
+
outline: none;
|
|
80
|
+
border-color: var(--accent);
|
|
81
|
+
background-color: var(--panel);
|
|
82
|
+
box-shadow: 0 0 0 1px var(--accent);
|
|
83
|
+
}
|
|
84
|
+
.wg-select:disabled {
|
|
85
|
+
opacity: 0.55;
|
|
86
|
+
cursor: not-allowed;
|
|
87
|
+
}
|
|
88
|
+
.wg-select--compact {
|
|
89
|
+
max-width: 160px;
|
|
90
|
+
}
|
|
91
|
+
body[data-theme="dark"] .wg-select {
|
|
92
|
+
color-scheme: dark;
|
|
93
|
+
background-image: ${UI_SELECT_CHEVRON_DARK};
|
|
94
|
+
}
|
|
95
|
+
.toolbar .wg-select {
|
|
96
|
+
flex-shrink: 0;
|
|
97
|
+
}
|
|
98
|
+
`;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { renderUiButton } from './atoms/button.mjs';
|
|
2
|
+
import { renderUiSelect } from './atoms/select.mjs';
|
|
3
|
+
import { renderUiTabsGroup } from './molecules/tabs.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sidebar nav tab (Gripe DS unstyled shell — keeps .nav-tab CSS).
|
|
7
|
+
* @param {{ view: string, label: string, selected?: boolean, disabled?: boolean }} props
|
|
8
|
+
*/
|
|
9
|
+
export function renderNavTab({ view, label, selected = false, disabled = false }) {
|
|
10
|
+
return renderUiButton({
|
|
11
|
+
unstyled: true,
|
|
12
|
+
className: 'nav-tab',
|
|
13
|
+
label,
|
|
14
|
+
disabled,
|
|
15
|
+
attrs: {
|
|
16
|
+
'data-view': view,
|
|
17
|
+
'aria-selected': selected ? 'true' : 'false',
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function renderThemeToggleButton() {
|
|
23
|
+
return renderUiButton({
|
|
24
|
+
unstyled: true,
|
|
25
|
+
id: 'theme-toggle',
|
|
26
|
+
className: 'theme-toggle',
|
|
27
|
+
label: 'Тёмная тема',
|
|
28
|
+
attrs: { 'aria-pressed': 'false' },
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function renderDetailCloseButton() {
|
|
33
|
+
return renderUiButton({
|
|
34
|
+
unstyled: true,
|
|
35
|
+
id: 'detail-close',
|
|
36
|
+
className: 'detail-close',
|
|
37
|
+
label: 'Закрыть',
|
|
38
|
+
ariaLabel: 'Закрыть подробности',
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function renderDetailSubCloseButton() {
|
|
43
|
+
return renderUiButton({
|
|
44
|
+
unstyled: true,
|
|
45
|
+
id: 'detail-sub-close',
|
|
46
|
+
className: 'detail-close',
|
|
47
|
+
label: 'Закрыть',
|
|
48
|
+
ariaLabel: 'Закрыть описание узла L2',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function renderAgentRunDockCloseButton() {
|
|
53
|
+
return renderUiButton({
|
|
54
|
+
unstyled: true,
|
|
55
|
+
id: 'agent-run-dock-close',
|
|
56
|
+
className: 'agent-dock-close',
|
|
57
|
+
label: '×',
|
|
58
|
+
ariaLabel: 'Свернуть dock',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function renderAgentRunFooterButtons() {
|
|
63
|
+
return [
|
|
64
|
+
renderUiButton({
|
|
65
|
+
id: 'agent-run-retry',
|
|
66
|
+
variant: 'secondary',
|
|
67
|
+
size: 'sm',
|
|
68
|
+
label: 'Повтор',
|
|
69
|
+
}),
|
|
70
|
+
renderUiButton({
|
|
71
|
+
id: 'agent-run-open-task',
|
|
72
|
+
variant: 'primary',
|
|
73
|
+
size: 'sm',
|
|
74
|
+
label: 'Открыть задачу',
|
|
75
|
+
}),
|
|
76
|
+
].join('\n ');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function renderIntentComposerActionButtons() {
|
|
80
|
+
return [
|
|
81
|
+
renderUiButton({
|
|
82
|
+
variant: 'secondary',
|
|
83
|
+
size: 'sm',
|
|
84
|
+
label: 'Preview draft',
|
|
85
|
+
testId: 'intent-composer-propose',
|
|
86
|
+
attrs: { 'data-action': 'propose' },
|
|
87
|
+
}),
|
|
88
|
+
renderUiButton({
|
|
89
|
+
variant: 'primary',
|
|
90
|
+
size: 'sm',
|
|
91
|
+
label: 'Создать задачу',
|
|
92
|
+
testId: 'intent-composer-apply',
|
|
93
|
+
disabled: true,
|
|
94
|
+
attrs: { 'data-action': 'apply' },
|
|
95
|
+
}),
|
|
96
|
+
renderUiButton({
|
|
97
|
+
variant: 'flat',
|
|
98
|
+
size: 'sm',
|
|
99
|
+
label: 'Отменить',
|
|
100
|
+
testId: 'intent-composer-cancel',
|
|
101
|
+
attrs: { 'data-action': 'cancel' },
|
|
102
|
+
}),
|
|
103
|
+
].join('\n ');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function renderIntentDomainClearButton() {
|
|
107
|
+
return renderUiButton({
|
|
108
|
+
unstyled: true,
|
|
109
|
+
id: 'intent-domain-clear',
|
|
110
|
+
className: 'board-tab',
|
|
111
|
+
label: 'Сброс домена',
|
|
112
|
+
hidden: true,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function renderSearchModeSelect() {
|
|
117
|
+
return renderUiSelect({
|
|
118
|
+
id: 'search-mode',
|
|
119
|
+
className: 'wg-select--search-mode',
|
|
120
|
+
testId: 'semantic-search-mode',
|
|
121
|
+
ariaLabel: 'Semantic search mode',
|
|
122
|
+
options: [
|
|
123
|
+
{ value: 'local', label: 'local filter', selected: true },
|
|
124
|
+
{ value: 'lexical-v1', label: 'semantic lexical' },
|
|
125
|
+
{ value: 'hybrid-lexical-bm25-v1', label: 'semantic hybrid BM25' },
|
|
126
|
+
],
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function renderCycleFilterSelect() {
|
|
131
|
+
return renderUiSelect({
|
|
132
|
+
id: 'cycle-filter',
|
|
133
|
+
className: 'wg-select--compact',
|
|
134
|
+
ariaLabel: 'Цикл',
|
|
135
|
+
options: [
|
|
136
|
+
{ value: 'current', label: 'Текущий цикл', selected: true },
|
|
137
|
+
{ value: 'all', label: 'Все циклы' },
|
|
138
|
+
],
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function renderIntentDomainFilterSelect() {
|
|
143
|
+
return renderUiSelect({
|
|
144
|
+
id: 'intent-domain-filter',
|
|
145
|
+
className: 'wg-select--compact',
|
|
146
|
+
ariaLabel: 'Домен',
|
|
147
|
+
options: [{ value: '', label: 'Все домены', selected: true }],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function renderAnalyticsSubtabsShell() {
|
|
152
|
+
return renderUiTabsGroup({
|
|
153
|
+
className: 'workflow-subtabs analytics-subtabs',
|
|
154
|
+
testId: 'analytics-subtabs',
|
|
155
|
+
ariaLabel: 'Тип аналитики',
|
|
156
|
+
tabs: [
|
|
157
|
+
{
|
|
158
|
+
id: 'intake',
|
|
159
|
+
label: 'Разборы',
|
|
160
|
+
selected: true,
|
|
161
|
+
count: 0,
|
|
162
|
+
elementId: 'analytics-tab-intake',
|
|
163
|
+
dataAttrKey: 'data-analytics-tab',
|
|
164
|
+
countId: 'analytics-intake-tab-count',
|
|
165
|
+
},
|
|
166
|
+
{
|
|
167
|
+
id: 'closing',
|
|
168
|
+
label: 'Итоги эпиков',
|
|
169
|
+
count: 0,
|
|
170
|
+
elementId: 'analytics-tab-closing',
|
|
171
|
+
dataAttrKey: 'data-analytics-tab',
|
|
172
|
+
countId: 'analytics-closing-tab-count',
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function renderWorkflowSubtabsShell() {
|
|
179
|
+
return renderUiTabsGroup({
|
|
180
|
+
className: 'workflow-subtabs',
|
|
181
|
+
testId: 'workflow-subtabs',
|
|
182
|
+
ariaLabel: 'Бэклог и архив',
|
|
183
|
+
tabs: [
|
|
184
|
+
{
|
|
185
|
+
id: 'backlog',
|
|
186
|
+
label: 'Бэклог',
|
|
187
|
+
selected: true,
|
|
188
|
+
count: 0,
|
|
189
|
+
elementId: 'workflow-tab-backlog',
|
|
190
|
+
dataAttrKey: 'data-workflow-tab',
|
|
191
|
+
countId: 'workflow-backlog-tab-count',
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'archive',
|
|
195
|
+
label: 'Архив',
|
|
196
|
+
count: 0,
|
|
197
|
+
elementId: 'workflow-tab-archive',
|
|
198
|
+
dataAttrKey: 'data-workflow-tab',
|
|
199
|
+
countId: 'workflow-archive-tab-count',
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export function renderArchitectureSubtabsShell() {
|
|
206
|
+
return '';
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function renderWorkflowDisplayModeSelect() {
|
|
210
|
+
return renderUiSelect({
|
|
211
|
+
id: 'workflow-display-mode',
|
|
212
|
+
className: 'wg-select--compact',
|
|
213
|
+
ariaLabel: 'Режим списка задач',
|
|
214
|
+
options: [
|
|
215
|
+
{ value: 'epic-groups', label: 'Эпики', selected: true },
|
|
216
|
+
{ value: 'flat', label: 'Плоский' },
|
|
217
|
+
{ value: 'tree', label: 'Дерево' },
|
|
218
|
+
],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export function renderArchitectureGraphModeToggle() {
|
|
223
|
+
return (
|
|
224
|
+
'<div class="graph-canvas-mode-toggle" role="group" aria-label="Режим графа">' +
|
|
225
|
+
renderUiButton({
|
|
226
|
+
unstyled: true,
|
|
227
|
+
label: 'Конвейер',
|
|
228
|
+
className: 'is-active',
|
|
229
|
+
attrs: { 'data-graph-canvas-mode': 'pipeline', 'aria-pressed': 'true' },
|
|
230
|
+
}) +
|
|
231
|
+
renderUiButton({
|
|
232
|
+
unstyled: true,
|
|
233
|
+
label: 'Полный',
|
|
234
|
+
attrs: { 'data-graph-canvas-mode': 'full', 'aria-pressed': 'false' },
|
|
235
|
+
}) +
|
|
236
|
+
'</div>'
|
|
237
|
+
);
|
|
238
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export function escapeHtml(value) {
|
|
2
|
+
return String(value ?? '')
|
|
3
|
+
.replace(/&/g, '&')
|
|
4
|
+
.replace(/</g, '<')
|
|
5
|
+
.replace(/>/g, '>')
|
|
6
|
+
.replace(/"/g, '"');
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function escapeHtmlAttr(value) {
|
|
10
|
+
return escapeHtml(value).replace(/'/g, ''');
|
|
11
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { escapeHtml, escapeHtmlAttr } from '../htmlEscape.mjs';
|
|
2
|
+
|
|
3
|
+
const SIZE_CLASS = {
|
|
4
|
+
xs: 'wg-rating--xs',
|
|
5
|
+
sm: 'wg-rating--sm',
|
|
6
|
+
md: 'wg-rating--md',
|
|
7
|
+
card: 'wg-rating--card',
|
|
8
|
+
lg: 'wg-rating--lg',
|
|
9
|
+
xl: 'wg-rating--xl',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const COLOR_CLASS = {
|
|
13
|
+
blue: 'wg-rating--blue',
|
|
14
|
+
'yellow-orange': 'wg-rating--yellow-orange',
|
|
15
|
+
black: 'wg-rating--black',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @param {{ value?: number | string, size?: string, color?: string, showValue?: boolean, testId?: string }} props
|
|
20
|
+
*/
|
|
21
|
+
export function renderUiRating(props = {}) {
|
|
22
|
+
const size = SIZE_CLASS[props.size] ? props.size : 'md';
|
|
23
|
+
const color = COLOR_CLASS[props.color] ? props.color : 'blue';
|
|
24
|
+
const raw = props.value ?? 0;
|
|
25
|
+
const hasPlus = String(raw).includes('+');
|
|
26
|
+
const numeric = hasPlus ? 5 : Math.max(0, Math.min(5, Number(raw) || 0));
|
|
27
|
+
const filled = hasPlus ? 5 : Math.round(numeric);
|
|
28
|
+
const stars = Array.from({ length: 5 }, (_, index) =>
|
|
29
|
+
`<span class="wg-rating__star${index < filled ? ' is-filled' : ''}" aria-hidden="true">★</span>`,
|
|
30
|
+
).join('');
|
|
31
|
+
const valueHtml = props.showValue
|
|
32
|
+
? `<span class="wg-rating__value">${escapeHtml(hasPlus ? '5+' : String(numeric))}</span>`
|
|
33
|
+
: '';
|
|
34
|
+
const testId = props.testId ?? 'ui-rating';
|
|
35
|
+
return `<div class="wg-rating ${SIZE_CLASS[size]} ${COLOR_CLASS[color]}" data-testid="${escapeHtmlAttr(testId)}" role="img" aria-label="Rating ${escapeHtmlAttr(String(raw))}">${valueHtml}<span class="wg-rating__stars">${stars}</span></div>`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const UI_RATING_CSS = `
|
|
39
|
+
.wg-rating { display: inline-flex; align-items: center; gap: var(--ui-rating-gap, 0.375rem); }
|
|
40
|
+
.wg-rating__stars { display: inline-flex; gap: 2px; color: rgb(var(--brand-border-rgb, 80 80 80)); }
|
|
41
|
+
.wg-rating__star.is-filled { color: rgb(var(--ui-rating-active-rgb, 0 102 255)); }
|
|
42
|
+
.wg-rating--yellow-orange .wg-rating__star.is-filled { color: rgb(245 158 11); }
|
|
43
|
+
.wg-rating--black .wg-rating__star.is-filled { color: rgb(0 0 0); }
|
|
44
|
+
.wg-rating__value { font-size: var(--ui-rating-value-font-size, 0.875rem); margin-right: 4px; }
|
|
45
|
+
.wg-rating--sm .wg-rating__stars { font-size: 0.875rem; }
|
|
46
|
+
.wg-rating--md .wg-rating__stars { font-size: 1.125rem; }
|
|
47
|
+
.wg-rating--lg .wg-rating__stars { font-size: 1.75rem; }
|
|
48
|
+
`;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { renderUiButton } from '../atoms/button.mjs';
|
|
2
|
+
import { escapeHtml, escapeHtmlAttr } from '../htmlEscape.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {{ tabs: Array<{ id: string, label: string, labelHtml?: string, selected?: boolean, count?: number }>, size?: string, testId?: string, ariaLabel?: string, className?: string }} props
|
|
6
|
+
*/
|
|
7
|
+
export function renderUiTabsGroup(props = {}) {
|
|
8
|
+
const tabs = props.tabs ?? [];
|
|
9
|
+
const sizeClass = props.size === 'sm' ? 'wg-tabs--sm' : props.size === 'lg' ? 'wg-tabs--lg' : 'wg-tabs--md';
|
|
10
|
+
const groupClass = ['wg-tabs', sizeClass, props.className ?? ''].filter(Boolean).join(' ');
|
|
11
|
+
const testId = props.testId ?? 'ui-tabs-group';
|
|
12
|
+
const triggers = tabs.map((tab) => renderUiTabsTrigger({
|
|
13
|
+
id: tab.id,
|
|
14
|
+
label: tab.label,
|
|
15
|
+
labelHtml: tab.labelHtml,
|
|
16
|
+
selected: tab.selected,
|
|
17
|
+
count: tab.count,
|
|
18
|
+
elementId: tab.elementId,
|
|
19
|
+
countId: tab.countId,
|
|
20
|
+
dataAttrKey: tab.dataAttrKey,
|
|
21
|
+
testId: tab.testId,
|
|
22
|
+
})).join('');
|
|
23
|
+
return `<div class="${escapeHtmlAttr(groupClass)}" role="tablist"${props.ariaLabel ? ` aria-label="${escapeHtmlAttr(props.ariaLabel)}"` : ''} data-testid="${escapeHtmlAttr(testId)}">${triggers}</div>`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @param {{
|
|
28
|
+
* id: string,
|
|
29
|
+
* label?: string,
|
|
30
|
+
* labelHtml?: string,
|
|
31
|
+
* selected?: boolean,
|
|
32
|
+
* count?: number | string,
|
|
33
|
+
* testId?: string,
|
|
34
|
+
* elementId?: string,
|
|
35
|
+
* countId?: string,
|
|
36
|
+
* dataAttrKey?: string,
|
|
37
|
+
* }} props
|
|
38
|
+
*/
|
|
39
|
+
export function renderUiTabsTrigger(props = {}) {
|
|
40
|
+
const selected = props.selected === true;
|
|
41
|
+
const dataKey = props.dataAttrKey ?? 'data-tab';
|
|
42
|
+
const countHtml = props.countId != null
|
|
43
|
+
? `<span id="${escapeHtmlAttr(props.countId)}" class="count">${escapeHtml(String(props.count ?? 0))}</span>`
|
|
44
|
+
: props.count != null
|
|
45
|
+
? `<span class="count">${escapeHtml(String(props.count))}</span>`
|
|
46
|
+
: '';
|
|
47
|
+
const labelHtml = props.labelHtml ?? (
|
|
48
|
+
countHtml
|
|
49
|
+
? `${escapeHtml(props.label ?? '')} ${countHtml}`
|
|
50
|
+
: escapeHtml(props.label ?? '')
|
|
51
|
+
);
|
|
52
|
+
return renderUiButton({
|
|
53
|
+
unstyled: true,
|
|
54
|
+
id: props.elementId,
|
|
55
|
+
className: `wg-tabs__trigger workflow-subtab board-tab${selected ? ' is-active' : ''}`,
|
|
56
|
+
labelHtml,
|
|
57
|
+
testId: props.testId ?? `ui-tabs-trigger-${props.id}`,
|
|
58
|
+
role: 'tab',
|
|
59
|
+
ariaSelected: selected ? 'true' : 'false',
|
|
60
|
+
attrs: {
|
|
61
|
+
[dataKey]: props.id,
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export const UI_TABS_CSS = `
|
|
67
|
+
.wg-tabs { display: flex; flex-wrap: wrap; gap: 4px; }
|
|
68
|
+
.wg-tabs__trigger { border-bottom: 2px solid transparent; border-radius: 0; }
|
|
69
|
+
.wg-tabs__trigger.is-active { border-bottom-color: rgb(var(--ui-accent-rgb, 0 102 255)); color: rgb(var(--ui-accent-rgb, 0 102 255)); font-weight: 600; }
|
|
70
|
+
`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderUiModal, UI_MODAL_CSS } from '../atoms/modal.mjs';
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { renderUiBadge, UI_BADGE_CSS } from '../atoms/badge.mjs';
|
|
2
|
+
import { renderUiButton, UI_BUTTON_CSS } from '../atoms/button.mjs';
|
|
3
|
+
import { renderUiIcon, UI_ICON_CSS } from '../atoms/icon.mjs';
|
|
4
|
+
import { renderUiTextInput, UI_INPUT_CSS } from '../atoms/input.mjs';
|
|
5
|
+
import { renderUiSelect, UI_SELECT_CSS } from '../atoms/select.mjs';
|
|
6
|
+
import { renderUiModal, UI_MODAL_CSS } from '../atoms/modal.mjs';
|
|
7
|
+
import { renderUiRating, UI_RATING_CSS } from '../molecules/rating.mjs';
|
|
8
|
+
import { renderUiTabsGroup, UI_TABS_CSS } from '../molecules/tabs.mjs';
|
|
9
|
+
import { escapeHtml } from '../htmlEscape.mjs';
|
|
10
|
+
|
|
11
|
+
export const UI_ATOM_REGISTRY = [
|
|
12
|
+
{
|
|
13
|
+
id: 'button',
|
|
14
|
+
layer: 'atom',
|
|
15
|
+
title: 'Button',
|
|
16
|
+
renderPreview: () => [
|
|
17
|
+
renderUiButton({ label: 'Primary', variant: 'primary', testId: 'ui-kit-button-primary' }),
|
|
18
|
+
renderUiButton({ label: 'Secondary', variant: 'secondary', testId: 'ui-kit-button-secondary' }),
|
|
19
|
+
renderUiButton({ label: 'Flat', variant: 'flat', testId: 'ui-kit-button-flat' }),
|
|
20
|
+
].join(' '),
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: 'badge',
|
|
24
|
+
layer: 'atom',
|
|
25
|
+
title: 'Badge',
|
|
26
|
+
renderPreview: () => [
|
|
27
|
+
renderUiBadge({ label: 'default', testId: 'ui-kit-badge-default' }),
|
|
28
|
+
renderUiBadge({ label: 'accent', tone: 'accent', testId: 'ui-kit-badge-accent' }),
|
|
29
|
+
renderUiBadge({ label: 'danger', tone: 'danger', testId: 'ui-kit-badge-danger' }),
|
|
30
|
+
].join(' '),
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'select',
|
|
34
|
+
layer: 'atom',
|
|
35
|
+
title: 'Select',
|
|
36
|
+
renderPreview: () => renderUiSelect({
|
|
37
|
+
testId: 'ui-kit-select',
|
|
38
|
+
ariaLabel: 'Demo select',
|
|
39
|
+
options: [
|
|
40
|
+
{ value: 'a', label: 'Option A', selected: true },
|
|
41
|
+
{ value: 'b', label: 'Option B' },
|
|
42
|
+
],
|
|
43
|
+
}),
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: 'text-input',
|
|
47
|
+
layer: 'atom',
|
|
48
|
+
title: 'Text input',
|
|
49
|
+
renderPreview: () => renderUiTextInput({ placeholder: 'Placeholder', testId: 'ui-kit-input' }),
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: 'icon',
|
|
53
|
+
layer: 'atom',
|
|
54
|
+
title: 'Icon',
|
|
55
|
+
renderPreview: () => renderUiIcon({ name: 'dot', testId: 'ui-kit-icon' }),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'modal',
|
|
59
|
+
layer: 'atom',
|
|
60
|
+
title: 'Modal',
|
|
61
|
+
renderPreview: () => renderUiModal({
|
|
62
|
+
title: 'Preview modal',
|
|
63
|
+
bodyHtml: '<p class="muted">Static preview for /dev/ui-kit</p>',
|
|
64
|
+
testId: 'ui-kit-modal',
|
|
65
|
+
}),
|
|
66
|
+
},
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
export const UI_MOLECULE_REGISTRY = [
|
|
70
|
+
{
|
|
71
|
+
id: 'rating',
|
|
72
|
+
layer: 'molecule',
|
|
73
|
+
title: 'Rating',
|
|
74
|
+
renderPreview: () => [
|
|
75
|
+
renderUiRating({ value: 4, size: 'md', showValue: true, testId: 'ui-kit-rating-md' }),
|
|
76
|
+
renderUiRating({ value: '5+', size: 'sm', color: 'yellow-orange', testId: 'ui-kit-rating-plus' }),
|
|
77
|
+
].join(' '),
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: 'tabs',
|
|
81
|
+
layer: 'molecule',
|
|
82
|
+
title: 'Tabs',
|
|
83
|
+
renderPreview: () => renderUiTabsGroup({
|
|
84
|
+
testId: 'ui-kit-tabs',
|
|
85
|
+
ariaLabel: 'Demo tabs',
|
|
86
|
+
tabs: [
|
|
87
|
+
{ id: 'drafts', label: 'Черновики', selected: true, count: 3 },
|
|
88
|
+
{ id: 'archive', label: 'Архив', count: 12 },
|
|
89
|
+
],
|
|
90
|
+
}),
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
export const UI_COMPONENT_REGISTRY = [...UI_ATOM_REGISTRY, ...UI_MOLECULE_REGISTRY];
|
|
95
|
+
|
|
96
|
+
export function renderUiKitComponentCss() {
|
|
97
|
+
return [UI_BUTTON_CSS, UI_BADGE_CSS, UI_SELECT_CSS, UI_INPUT_CSS, UI_ICON_CSS, UI_MODAL_CSS, UI_RATING_CSS, UI_TABS_CSS].join('\n');
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function renderUiKitPageHtml() {
|
|
101
|
+
const sidebar = UI_COMPONENT_REGISTRY.map((entry) =>
|
|
102
|
+
`<li><a href="#ui-kit-${escapeHtml(entry.id)}">${escapeHtml(entry.title)}</a></li>`,
|
|
103
|
+
).join('');
|
|
104
|
+
|
|
105
|
+
const sections = UI_COMPONENT_REGISTRY.map((entry) =>
|
|
106
|
+
`<section id="ui-kit-${escapeHtml(entry.id)}" class="ui-kit-section" data-testid="ui-kit-section-${escapeHtml(entry.id)}">` +
|
|
107
|
+
`<h2>${escapeHtml(entry.title)} <code>${escapeHtml(entry.id)}</code></h2>` +
|
|
108
|
+
`<div class="ui-kit-preview">${entry.renderPreview()}</div>` +
|
|
109
|
+
'</section>',
|
|
110
|
+
).join('');
|
|
111
|
+
|
|
112
|
+
return `<!doctype html>
|
|
113
|
+
<html lang="ru" data-theme="dark" data-iohasc-theme="workgraph-dark">
|
|
114
|
+
<head>
|
|
115
|
+
<meta charset="utf-8">
|
|
116
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
117
|
+
<title>Work Graph UI Kit</title>
|
|
118
|
+
<link rel="stylesheet" href="/assets/fonts/GraphikLCG/stylesheet.css">
|
|
119
|
+
<link rel="stylesheet" href="/assets/design-tokens-workgraph-dark.css">
|
|
120
|
+
<style>
|
|
121
|
+
html {
|
|
122
|
+
font-family: var(--brand-font-sans);
|
|
123
|
+
letter-spacing: 0.01em;
|
|
124
|
+
-webkit-font-smoothing: antialiased;
|
|
125
|
+
-moz-osx-font-smoothing: grayscale;
|
|
126
|
+
text-rendering: optimizeLegibility;
|
|
127
|
+
}
|
|
128
|
+
body { margin: 0; background: rgb(var(--brand-bg-rgb, 30 30 30)); color: rgb(var(--ui-text-rgb, 212 212 212)); font: var(--text-base)/var(--text-base-line-height) var(--brand-font-sans); }
|
|
129
|
+
.ui-kit-layout { display: grid; grid-template-columns: 220px 1fr; min-height: 100vh; }
|
|
130
|
+
.ui-kit-sidebar { border-right: 1px solid rgb(var(--brand-border-rgb, 60 60 60)); padding: 16px; }
|
|
131
|
+
.ui-kit-sidebar ul { list-style: none; margin: 0; padding: 0; }
|
|
132
|
+
.ui-kit-sidebar a { color: rgb(var(--ui-link-rgb, 0 102 255)); text-decoration: none; display: block; padding: 6px 0; }
|
|
133
|
+
.ui-kit-main { padding: 20px; }
|
|
134
|
+
.ui-kit-section { margin-bottom: 28px; padding-bottom: 20px; border-bottom: 1px solid rgb(var(--brand-border-rgb, 60 60 60)); }
|
|
135
|
+
.ui-kit-preview { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-top: 10px; }
|
|
136
|
+
.muted { color: rgb(var(--ui-muted-rgb, 157 157 157)); }
|
|
137
|
+
${renderUiKitComponentCss()}
|
|
138
|
+
</style>
|
|
139
|
+
</head>
|
|
140
|
+
<body data-testid="ui-kit-root">
|
|
141
|
+
<div class="ui-kit-layout">
|
|
142
|
+
<aside class="ui-kit-sidebar"><h1>UI Kit</h1><ul>${sidebar}</ul><p class="muted"><a href="/">← Backlog UI</a></p></aside>
|
|
143
|
+
<main class="ui-kit-main">${sections}</main>
|
|
144
|
+
</div>
|
|
145
|
+
</body>
|
|
146
|
+
</html>`;
|
|
147
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** @typedef {'default' | 'accent' | 'muted' | 'danger' | 'ok'} BadgeTone */
|
|
2
|
+
|
|
3
|
+
const STATUS_LABELS = {
|
|
4
|
+
backlog: 'Бэклог',
|
|
5
|
+
ready: 'Доступно агенту',
|
|
6
|
+
claimed: 'Взято',
|
|
7
|
+
doing: 'В работе',
|
|
8
|
+
in_progress: 'В работе',
|
|
9
|
+
verify: 'Проверка',
|
|
10
|
+
blocked: 'Блокер',
|
|
11
|
+
done: 'Завершено',
|
|
12
|
+
verified: 'Проверено',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {string | null | undefined} status
|
|
17
|
+
*/
|
|
18
|
+
export function statusLabel(status) {
|
|
19
|
+
const key = String(status ?? '').toLowerCase();
|
|
20
|
+
return STATUS_LABELS[key] ?? String(status ?? '');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* @param {string | null | undefined} status
|
|
25
|
+
* @param {string | null | undefined} [queueKind]
|
|
26
|
+
* @returns {BadgeTone}
|
|
27
|
+
*/
|
|
28
|
+
export function statusToBadgeTone(status, queueKind = null) {
|
|
29
|
+
if (queueKind === 'planned') return 'accent';
|
|
30
|
+
const key = String(status ?? '').toLowerCase();
|
|
31
|
+
if (key === 'ready' || key === 'claimed' || key === 'doing' || key === 'in_progress') return 'accent';
|
|
32
|
+
if (key === 'done' || key === 'verified') return 'ok';
|
|
33
|
+
if (key === 'blocked') return 'danger';
|
|
34
|
+
if (key === 'verify') return 'muted';
|
|
35
|
+
return 'default';
|
|
36
|
+
}
|