feat(usable): integrate current dbtool implementation snapshot
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
@@ -34,12 +34,13 @@
|
||||
## 原型包含内容
|
||||
|
||||
- 连接管理、schema browser、query editor、results、export、inspector 的稳定工作台分区
|
||||
- 明确的 `connect -> inspect -> query -> export` 引导式评审轨道
|
||||
- 按连接切换不同的 schema catalog、SQL 草稿标签、结果列和默认导出路径
|
||||
- `Running` / `Success` / `Empty` / `Error` / `Exported` 五种可预览执行态
|
||||
- Light / Dark 主题切换
|
||||
- schema 搜索过滤、连接切换、结果 / 历史 / 导出 / 问题面板切换
|
||||
- 当前界面状态到共享结构化契约的映射预览,其中 `running` 明确标记为前端暂态
|
||||
- 键盘快捷路径:`1` / `2` / `3` / `4` / `5`、`R`、`T`、`/`、`⌘/Ctrl+K`
|
||||
- 键盘快捷路径:`C` / `I` / `Q` / `E`、`1` / `2` / `3` / `4` / `5`、`R`、`T`、`/`、`⌘/Ctrl+K`
|
||||
|
||||
## 查看方式
|
||||
|
||||
@@ -65,6 +66,16 @@ http://127.0.0.1:4173
|
||||
|
||||
当前 `/` 会落到 `gui/index.html`,再从入口页进入工作台原型。
|
||||
|
||||
## 建议评审路径
|
||||
|
||||
推荐按以下顺序评审:
|
||||
|
||||
1. 用 `C` 或顶部 workflow strip 查看 `Connect`,确认目标卡片、健康态和工作区摘要
|
||||
2. 用 `I` 查看 `Inspect`,确认 schema tree、selection 和 inspector 同步
|
||||
3. 用 `Q` 查看 `Query`,确认 SQL 草稿、执行 banner 和结果区处于同一上下文
|
||||
4. 用 `R`、`1`–`5` 或 `⌘/Ctrl+K` 预览不同执行态
|
||||
5. 用 `E` 查看 `Export`,确认默认路径、empty guidance 与 blocked export 行为
|
||||
|
||||
## 界面验收
|
||||
|
||||
验收人应重点确认:
|
||||
@@ -76,6 +87,7 @@ http://127.0.0.1:4173
|
||||
5. 切换连接后,顶部摘要、中部工作区和右侧 inspector 是否同步更新
|
||||
6. 切换连接后,schema tree、query tab 标签、结果列与默认导出路径是否也同步切换到对应驱动上下文
|
||||
7. Light / Dark 两种主题下信息密度、对比度和可读性是否仍然稳定
|
||||
8. `⌘/Ctrl+K` 快捷操作面板是否能帮助 QA 快速预览状态与连接切换
|
||||
9. 文档中的后端契约需求是否仍可映射到 CLI 已定义产品概念
|
||||
10. `Contract` 面板是否能帮助 QA 与 Backend 快速核对 `query` / `export` envelope,以及 `running` 暂态边界
|
||||
8. 顶部 workflow strip 和 `C` / `I` / `Q` / `E` 是否能帮助 QA 明确当前正在评审哪一步主流程
|
||||
9. `⌘/Ctrl+K` 快捷操作面板是否能帮助 QA 快速预览状态与连接切换
|
||||
10. 文档中的后端契约需求是否仍可映射到 CLI 已定义产品概念
|
||||
11. `Contract` 面板是否能帮助 QA 与 Backend 快速核对 `query` / `export` envelope,以及 `running` 暂态边界
|
||||
|
||||
@@ -54,6 +54,16 @@
|
||||
</ol>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Review Path</p>
|
||||
<h2>可按主流程逐步评审</h2>
|
||||
<ul>
|
||||
<li>Connect → Inspect → Query → Export</li>
|
||||
<li>工作流轨道与高亮区域同步</li>
|
||||
<li>`C` / `I` / `Q` / `E` 可快速切换评审步骤</li>
|
||||
</ul>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Acceptance</p>
|
||||
<h2>QA 重点确认</h2>
|
||||
|
||||
94
gui/prototype/app.js
vendored
94
gui/prototype/app.js
vendored
@@ -495,6 +495,8 @@ limit 25;`,
|
||||
const connectionCards = document.querySelectorAll(".connection-card");
|
||||
const bottomTabs = document.querySelectorAll(".bottom-tab");
|
||||
const tabContents = document.querySelectorAll(".tab-content");
|
||||
const workflowStepButtons = document.querySelectorAll("[data-workflow-step]");
|
||||
const reviewPanels = document.querySelectorAll("[data-review-panel]");
|
||||
|
||||
const activeConnection = document.getElementById("active-connection");
|
||||
const driverPill = document.getElementById("driver-pill");
|
||||
@@ -556,6 +558,7 @@ const commandCloseButton = document.getElementById("command-close");
|
||||
const commandStateButtons = document.querySelectorAll("[data-command-state]");
|
||||
const commandConnectionButtons = document.querySelectorAll("[data-command-connection]");
|
||||
const commandActionButtons = document.querySelectorAll("[data-command-action]");
|
||||
const workflowCopy = document.getElementById("workflow-copy");
|
||||
|
||||
const appState = {
|
||||
connection: "pg-prod-readonly",
|
||||
@@ -563,9 +566,17 @@ const appState = {
|
||||
selection: "public.orders",
|
||||
activeState: "success",
|
||||
activeTab: "results",
|
||||
workflowStep: "connect",
|
||||
runTimer: null,
|
||||
};
|
||||
|
||||
const workflowStepCopy = {
|
||||
connect: "当前聚焦 Connect:先确认活动目标、环境标签和连接摘要清晰可见。",
|
||||
inspect: "当前聚焦 Inspect:核对 schema browser、当前对象和 inspector 元数据保持同步。",
|
||||
query: "当前聚焦 Query:确认 SQL 草稿、执行反馈和结果区在同一工作台上下文里。",
|
||||
export: "当前聚焦 Export:复核默认导出路径、空结果说明和错误态禁用约束。",
|
||||
};
|
||||
|
||||
function getActiveConnectionCard() {
|
||||
return document.querySelector(`.connection-card[data-connection="${appState.connection}"]`);
|
||||
}
|
||||
@@ -618,6 +629,20 @@ function setActiveTab(tabName) {
|
||||
});
|
||||
}
|
||||
|
||||
function renderWorkflowStep() {
|
||||
workflowStepButtons.forEach((button) => {
|
||||
const isActive = button.dataset.workflowStep === appState.workflowStep;
|
||||
button.classList.toggle("is-active", isActive);
|
||||
button.setAttribute("aria-pressed", String(isActive));
|
||||
});
|
||||
|
||||
reviewPanels.forEach((panel) => {
|
||||
panel.classList.toggle("is-review-focus", panel.dataset.reviewPanel === appState.workflowStep);
|
||||
});
|
||||
|
||||
workflowCopy.textContent = workflowStepCopy[appState.workflowStep];
|
||||
}
|
||||
|
||||
function renderToolbarTabs() {
|
||||
const fixture = getWorkspaceFixture();
|
||||
|
||||
@@ -1095,6 +1120,7 @@ function renderAll() {
|
||||
renderConnection();
|
||||
renderSelection();
|
||||
renderThemeLabel();
|
||||
renderWorkflowStep();
|
||||
renderQuery();
|
||||
setActiveTab(appState.activeTab);
|
||||
}
|
||||
@@ -1124,6 +1150,26 @@ function syncSelection(selection) {
|
||||
renderSelection();
|
||||
}
|
||||
|
||||
function setWorkflowStep(step, { syncTab = false, status = null } = {}) {
|
||||
appState.workflowStep = step;
|
||||
|
||||
if (syncTab) {
|
||||
if (step === "query") {
|
||||
setActiveTab("results");
|
||||
}
|
||||
|
||||
if (step === "export") {
|
||||
setActiveTab("export");
|
||||
}
|
||||
}
|
||||
|
||||
renderWorkflowStep();
|
||||
|
||||
if (status) {
|
||||
statusMessage.textContent = status;
|
||||
}
|
||||
}
|
||||
|
||||
function setQuery(queryKey, syncState = true) {
|
||||
const preset = getQueryPreset(queryKey);
|
||||
appState.query = queryKey;
|
||||
@@ -1136,6 +1182,7 @@ function setQuery(queryKey, syncState = true) {
|
||||
|
||||
function startRunPreview() {
|
||||
clearRunTimer();
|
||||
setWorkflowStep("query", { syncTab: true });
|
||||
appState.activeState = "running";
|
||||
setActiveTab("results");
|
||||
renderState();
|
||||
@@ -1158,7 +1205,9 @@ connectionCards.forEach((card) => {
|
||||
schemaSearch.value = "";
|
||||
setActiveTab("results");
|
||||
renderAll();
|
||||
statusMessage.textContent = `${card.dataset.connection} loaded. Catalog, draft labels, and inspector now reflect the active target.`;
|
||||
setWorkflowStep("connect", {
|
||||
status: `${card.dataset.connection} loaded. Catalog, draft labels, and inspector now reflect the active target.`,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1170,7 +1219,9 @@ schemaTree.addEventListener("click", (event) => {
|
||||
}
|
||||
|
||||
syncSelection(item.dataset.selection);
|
||||
statusMessage.textContent = `Selection changed to ${item.dataset.selection}. Inspector and workspace stay aligned.`;
|
||||
setWorkflowStep("inspect", {
|
||||
status: `Selection changed to ${item.dataset.selection}. Inspector and workspace stay aligned.`,
|
||||
});
|
||||
});
|
||||
|
||||
toolbarTabsContainer.addEventListener("click", (event) => {
|
||||
@@ -1181,6 +1232,7 @@ toolbarTabsContainer.addEventListener("click", (event) => {
|
||||
}
|
||||
|
||||
clearRunTimer();
|
||||
setWorkflowStep("query", { syncTab: true });
|
||||
setActiveTab("results");
|
||||
setQuery(tab.dataset.query, true);
|
||||
});
|
||||
@@ -1191,6 +1243,21 @@ bottomTabs.forEach((tab) => {
|
||||
});
|
||||
});
|
||||
|
||||
workflowStepButtons.forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const step = button.dataset.workflowStep;
|
||||
const syncTab = step === "query" || step === "export";
|
||||
const statusMap = {
|
||||
connect: "Connect review active. Confirm target health, environment label, and workspace summary.",
|
||||
inspect: "Inspect review active. Check schema tree, selection, and inspector metadata alignment.",
|
||||
query: "Query review active. Validate SQL draft, execution banner, and results context together.",
|
||||
export: "Export review active. Check default path, blocked states, and empty-result guidance.",
|
||||
};
|
||||
|
||||
setWorkflowStep(step, { syncTab, status: statusMap[step] });
|
||||
});
|
||||
});
|
||||
|
||||
runQueryButton.addEventListener("click", () => {
|
||||
startRunPreview();
|
||||
});
|
||||
@@ -1205,6 +1272,7 @@ cycleStateButton.addEventListener("click", () => {
|
||||
|
||||
function activateExport(format) {
|
||||
clearRunTimer();
|
||||
setWorkflowStep("export", { syncTab: true });
|
||||
|
||||
if (appState.activeState === "running") {
|
||||
renderState();
|
||||
@@ -1321,10 +1389,32 @@ document.addEventListener("keydown", (event) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!commandPalette.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.target === schemaSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
const workflowHotkeyMap = {
|
||||
c: "connect",
|
||||
i: "inspect",
|
||||
q: "query",
|
||||
e: "export",
|
||||
};
|
||||
|
||||
const workflowStep = workflowHotkeyMap[event.key.toLowerCase()];
|
||||
|
||||
if (workflowStep && !event.metaKey && !event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
setWorkflowStep(workflowStep, {
|
||||
syncTab: workflowStep === "query" || workflowStep === "export",
|
||||
status: `${workflowStep[0].toUpperCase()}${workflowStep.slice(1)} review active.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.key === "/") {
|
||||
event.preventDefault();
|
||||
schemaSearch.focus();
|
||||
|
||||
@@ -56,9 +56,37 @@
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="workflow-strip" aria-label="MVP workflow review">
|
||||
<div>
|
||||
<p class="eyebrow">Guided Review</p>
|
||||
<h2>MVP Workflow</h2>
|
||||
</div>
|
||||
<div class="workflow-steps">
|
||||
<button class="workflow-step is-active" data-workflow-step="connect">
|
||||
<strong>Connect</strong>
|
||||
<span>C · 检查目标选择、环境标签和连接健康态</span>
|
||||
</button>
|
||||
<button class="workflow-step" data-workflow-step="inspect">
|
||||
<strong>Inspect</strong>
|
||||
<span>I · 核对 schema browser、selection 和 inspector 一致性</span>
|
||||
</button>
|
||||
<button class="workflow-step" data-workflow-step="query">
|
||||
<strong>Query</strong>
|
||||
<span>Q · 评审 SQL 草稿、执行反馈和结果切换</span>
|
||||
</button>
|
||||
<button class="workflow-step" data-workflow-step="export">
|
||||
<strong>Export</strong>
|
||||
<span>E · 复核导出状态、默认路径和禁用约束</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="workflow-copy" id="workflow-copy">
|
||||
当前聚焦 Connect:先确认活动目标、环境标签和连接摘要清晰可见。
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<main class="workspace">
|
||||
<aside class="panel panel--nav">
|
||||
<section class="panel-section">
|
||||
<section class="panel-section" data-review-panel="connect">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<p class="eyebrow">Connection Manager</p>
|
||||
@@ -136,7 +164,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="panel-section">
|
||||
<section class="panel-section" data-review-panel="inspect">
|
||||
<div class="section-head">
|
||||
<div>
|
||||
<p class="eyebrow">Schema Browser</p>
|
||||
@@ -164,7 +192,7 @@
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<section class="panel panel--main">
|
||||
<section class="panel panel--main" data-review-panel="query">
|
||||
<div class="editor-toolbar">
|
||||
<div class="toolbar-tabs" id="toolbar-tabs"></div>
|
||||
|
||||
@@ -229,7 +257,7 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="bottom-panel">
|
||||
<section class="bottom-panel" data-review-panel="export">
|
||||
<div class="bottom-tabs">
|
||||
<button class="bottom-tab is-active" data-tab="results">Results</button>
|
||||
<button class="bottom-tab" data-tab="history">History</button>
|
||||
@@ -291,8 +319,12 @@
|
||||
</section>
|
||||
|
||||
<footer class="status-bar">
|
||||
<span id="status-message" aria-live="polite">Ready for review. Use 1–5 to preview states, `R` to simulate run, `T` to switch theme, `/` to focus search.</span>
|
||||
<span id="status-message" aria-live="polite">Ready for review. Use C / I / Q / E to move through the workflow, 1–5 to preview states, `R` to simulate run, `T` to switch theme, `/` to focus search.</span>
|
||||
<div class="status-shortcuts">
|
||||
<span>C Connect</span>
|
||||
<span>I Inspect</span>
|
||||
<span>Q Query</span>
|
||||
<span>E Export</span>
|
||||
<span>1 Running</span>
|
||||
<span>2 Success</span>
|
||||
<span>3 Empty</span>
|
||||
|
||||
@@ -150,6 +150,67 @@ select {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.workflow-strip {
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
gap: 14px 18px;
|
||||
align-items: start;
|
||||
padding: 16px 18px;
|
||||
margin-bottom: 16px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--panel);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.workflow-strip h2 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
|
||||
.workflow-steps {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.workflow-step {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
background: var(--panel-muted);
|
||||
color: inherit;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
transition: border-color 120ms ease, background 120ms ease, transform 120ms ease;
|
||||
}
|
||||
|
||||
.workflow-step:hover,
|
||||
.workflow-step:focus-visible {
|
||||
border-color: color-mix(in srgb, var(--accent) 36%, var(--border));
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.workflow-step.is-active {
|
||||
border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
|
||||
background: var(--accent-soft);
|
||||
}
|
||||
|
||||
.workflow-step span,
|
||||
.workflow-copy {
|
||||
color: var(--muted);
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.workflow-copy {
|
||||
grid-column: 2;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.summary-card {
|
||||
padding: 16px 18px;
|
||||
border: 1px solid var(--border);
|
||||
@@ -188,6 +249,20 @@ select {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-review-panel] {
|
||||
transition: border-color 120ms ease, background 120ms ease, box-shadow 120ms ease;
|
||||
}
|
||||
|
||||
[data-review-panel].is-review-focus {
|
||||
border-color: color-mix(in srgb, var(--accent) 42%, var(--border));
|
||||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--accent) 28%, transparent), var(--shadow);
|
||||
}
|
||||
|
||||
.panel-section.is-review-focus,
|
||||
.bottom-panel.is-review-focus {
|
||||
background: color-mix(in srgb, var(--accent-soft) 48%, var(--panel));
|
||||
}
|
||||
|
||||
.panel--nav,
|
||||
.panel--side {
|
||||
display: flex;
|
||||
@@ -703,6 +778,18 @@ th {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.workflow-strip {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.workflow-copy {
|
||||
grid-column: auto;
|
||||
}
|
||||
|
||||
.workflow-steps {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.workspace {
|
||||
grid-template-columns: 250px minmax(520px, 1fr) 250px;
|
||||
}
|
||||
@@ -713,7 +800,8 @@ th {
|
||||
.status-bar,
|
||||
.result-summary,
|
||||
.context-strip,
|
||||
.workspace-summary {
|
||||
.workspace-summary,
|
||||
.workflow-steps {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user