v0.7.6: PostgreSQL object management, keychain toggle, tabbed Objects workspace (#12)

* [P1-T1] Change::Ddl Rust variant + execute_change arm

* [P1-T2] Frontend ddl change type + objectCrud capability

* [P1-T3] use_keychain data model + migration v8

* [P2-T1] object_crud skeleton + validators + build_ddl dispatch

* [P2-T2] Sequence builders

* [P2-T3] Enum builders (no value removal)

* [P2-T4] View / matview / extension builders

* [P2-T5] Index + constraint builders

* [P2-T6] Function / procedure + trigger builders

* [P2-T7] build_object_ddl + get_available_extensions commands + wrappers

* [P3-T1] conditional keychain + session passwords

* [P3-T2] keychain-off password prompt on connect

* [P3-T3] default-ON keychain opt-out + tooltip + modal conditional

* [P3-T4] ddl queue card + after-commit refetch

* [P4-T1] ObjectCrudDialog shell

* [P4-T2] SequenceForm

* [P4-T3] EnumForm with no-removal note

* [P4-T4] ExtensionForm with available-extensions picker

* [P4-T5] ViewForm (view + materialized view)

* [P5-T1] IndexForm with column picker

* [P5-T2] ConstraintForm (check/unique/pk/fk) + ColumnPicker

* [P5-T3] FunctionForm (function + procedure)

* [P5-T4] TriggerForm with trigger-function picker

* [P5-T5] ObjectContextMenu + Explorer/TableOverflowMenu CRUD wiring

* [P6-T1] object tab type + openObjectTab dedup

* [P6-T2] extract ObjectDetail for object tabs

* [P6-T3] Objects view two-pane sidebar + workspace

* [P6-T4] object-tab content + per-type tab icons

* [P7-T1] Version bump 0.7.5 -> 0.7.6

* [P7-T2] docs sync README/ROADMAP/AGENTS for v0.7.6

* [P7-T3] chore: Cargo.lock version sync 0.7.5 -> 0.7.6

* fix(ui): object tab icon stacks above name (preflight svg block)

* fix(ui): optically center object tab icon with name

* fix(ui): object tab icon matches query/table icon handling

* [UI-POLISH-1] objectForm tab type + openFormTab store action

* [UI-POLISH-2] ObjectFormTab + KindForm with Visual/SQL toggle

* [UI-POLISH-3] route create/edit through form tabs; remove modal

* docs: create/edit now open as form tabs (AGENTS sync)

* [FB-1] follow app styling patterns + schema dropdown in forms

* [FB-2] Monaco editor for function body + view definition

* [FB-3] form tabs styled like viewers + in-cell editing

* [FB-5] focus outline scoped to input area (label excluded)

* [FB-6] no amber focus outline on Monaco body/definition rows

* [FB-7] header dedupe + schema default + full edit prefill

* [FB-8] SQL view in read-only Monaco editor

* docs: roadmap — table create/edit + relationships (next)

* docs: roadmap — Admin follow-up is 0.7.7 (next after 0.7.6)
This commit is contained in:
2026-08-06 22:56:49 +08:00
committed by GitHub
parent 8d8ed78202
commit 0c74d77e75
86 changed files with 7094 additions and 1165 deletions
@@ -0,0 +1,113 @@
import { render, screen, fireEvent } from "@testing-library/react";
import { describe, it, expect, vi } from "vitest";
import { useState } from "react";
import { SequenceForm } from "./SequenceForm";
import type { DdlParams } from "../../../lib/objectCrud";
function StatefulSequenceForm({ initialParams }: { initialParams: DdlParams }) {
const [params, setParams] = useState(initialParams);
return <SequenceForm params={params} onChange={setParams} />;
}
describe("SequenceForm", () => {
it("renders create fields and emits params on change", () => {
const onChange = vi.fn();
render(
<SequenceForm
params={{
schema: "public",
name: "s",
action: {
op: "create",
increment: "1",
min_value: "1",
max_value: "9",
start: "1",
cycle: false,
},
}}
onChange={onChange}
/>,
);
expect(screen.getByPlaceholderText("Sequence name")).toHaveValue("s");
fireEvent.change(screen.getByPlaceholderText("Sequence name"), {
target: { value: "s2" },
});
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({ name: "s2" }),
);
});
it("renders a schema dropdown when schemas are provided", () => {
const onChange = vi.fn();
render(
<SequenceForm
params={{ schema: "public", name: "s", action: { op: "create" } }}
schemas={["public", "utils"]}
onChange={onChange}
/>,
);
const select = screen.getByLabelText("Schema");
expect(select).toBeInTheDocument();
expect(screen.getByRole("option", { name: "public" })).toBeInTheDocument();
expect(screen.getByRole("option", { name: "utils" })).toBeInTheDocument();
fireEvent.change(select, { target: { value: "utils" } });
expect(onChange).toHaveBeenCalledWith(
expect.objectContaining({ schema: "utils" }),
);
});
it("the editing outline wraps only the value area, not the label cell", () => {
const onChange = vi.fn();
render(
<SequenceForm
params={{ schema: "public", name: "s", action: { op: "create" } }}
onChange={onChange}
/>,
);
// The children wrapper (direct parent of the input) carries the amber
// focus-within editing outline, exactly like a grid editing cell.
const input = screen.getByPlaceholderText("Sequence name");
const valueArea = input.parentElement;
expect(valueArea).not.toBeNull();
expect(valueArea!.className).toContain("focus-within:outline");
expect(valueArea!.className).toContain("focus-within:outline-amber-400");
expect(valueArea!.className).toContain("focus-within:outline-offset-[-2px]");
// The label cell must stay clean: no ancestor of the label may carry
// the editing outline (regression: the old row-level outline lit up the
// whole row, label cell included).
const label = screen.getByText("Name");
expect(label.closest('[class*="focus-within:outline"]')).toBeNull();
});
it("switching to restart shows only the with-field", () => {
render(
<StatefulSequenceForm
initialParams={{
schema: "public",
name: "s",
action: {
op: "create",
increment: "1",
min_value: "1",
max_value: "9",
start: "1",
cycle: false,
},
}}
/>,
);
const opSelect = screen.getByLabelText("Operation");
fireEvent.change(opSelect, { target: { value: "restart" } });
expect(screen.getByPlaceholderText("Restart with")).toBeInTheDocument();
expect(screen.queryByPlaceholderText("Increment")).not.toBeInTheDocument();
expect(screen.queryByPlaceholderText("Start")).not.toBeInTheDocument();
expect(screen.queryByLabelText("CYCLE")).not.toBeInTheDocument();
});
});