feat(usable): integrate current dbtool implementation snapshot
Some checks failed
release-smoke / macos-13 / x86_64-apple-darwin (push) Has been cancelled
release-smoke / ubuntu-latest / x86_64-unknown-linux-gnu (push) Has been cancelled
release-smoke / windows-latest / x86_64-pc-windows-msvc (push) Has been cancelled

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Paperclip CTO
2026-04-02 08:26:18 +00:00
parent a28dab4cd9
commit d5f69462b0
73 changed files with 5895 additions and 322 deletions

View File

@@ -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` 暂态边界

View File

@@ -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
View File

@@ -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();

View File

@@ -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 15 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, 15 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>

View File

@@ -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;
}