Skip to content

Catch up with the Druks Model and page surface - #7

Merged
czpython merged 1 commit into
mainfrom
commonzenpython/adopt-druks-model-surface
Sep 27, 2026
Merged

czpython merged 1 commit into
mainfrom
commonzenpython/adopt-druks-model-surface

Conversation

@czpython

@czpython czpython commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

Summary

Druks now gives app rows a Model base with get, get_or_none, filter, and all (czpython/druks#736, czpython/druks#739). A StoredSubject gets its table, timestamps, board, and 404 (czpython/druks#720). A subject names itself with __str__, a model declares its order on the class line, and a Literal column is stored under a CHECK (czpython/druks#738). A page shows where the work stands with ui.SubjectStatus, and druks check-app checks an app with no database (czpython/druks#744). Panel moves to that surface. Decision and its pages keep only what is their own.

Model

  • Decision drops __tablename__, created_at, create(), get(), get_summary(), list_summaries(), and list_recent(). Druks now supplies each of these. The table is panel_decision. updated_at stamps itself.
  • __str__ returns the title, so runs, Activity, and the board show the decision's title, not decision 7. ordering=("-created_at", "-id") lists the newest decision first on the board and in all().
  • The Decisions page and GET /api/panel/decisions read with Decision.all(). They list every decision, not only the newest 100. This matches the page's description: "Every question this panel has weighed."
  • recommendation and outcome are Mapped[DecisionAction | None]. The database refuses any value except proceed, revise, or pass.
  • DecisionSummary is a plain Schema (id, title, question, recommendation, outcome, createdAt). The list route returns the rows, and the response model serializes them. The response no longer carries key.
  • save_panel() and save_outcome() write through save().
  • The two JSONB columns keep mapped_column(JSONB) because they are typed list[dict[str, Any]] and dict[str, Any] | None, which the Druks docs name this way.

Pages and routes

  • record_outcome and the decision page read the row with Decision.get(id=...). A missing decision is a 404 on the route. On the page, it is an empty state that links back to Decisions. The inline 404 and the "No such decision" branch are gone.
  • The board's "Panel" column and the decision page's "Panel" fact use ui.SubjectStatus(decision, working="deliberating"). Druks reads every status when it serves the page, and the shell writes the word. _run_word() and the board's get_statuses() read are gone. The words are now "needs you" in place of "your call", and "idle" in place of "not started". The shell shows a failed run as "failed" with its message.
  • The board's table takes empty=ui.EmptyState(...) in place of empty_text, which Druks removed. The decision link on each row takes its words from the title.
  • The decision page no longer adds its own "Everything Druks did" link. The shell adds that link to every page that follows one subject.

Migration and dependencies

  • One regenerated panel_0001 creates panel_decision with one CHECK on each verdict column. No install holds Panel data, so the old revision is deleted, not migrated.
  • The README and AGENTS.md list uv run druks check-app panel as a gate.
  • Require druks>=0.8.0.

Before you merge

  • Relock after Druks 0.8.0 is published. uv.lock still pins 0.6.0 because druks>=0.8.0 does not resolve yet. Run uv lock and add the lockfile change to this commit.
  • An install that ran the old panel_0001 must be re-initialized. Drop panel_decisions and alembic_version_panel, then run druks init-db. The new revision has the same ID, so Alembic would skip it otherwise.

Verification

Ran in a fresh environment with editable installs of Druks main (beed0baa) and this branch:

  • ruff check, ruff format --check, pyright, and druks check-app panel: clean
  • druks makemigrations panel against a scratch database at the Druks head generates this exact revision under both the locked SQLAlchemy 2.0.51 / Alembic 1.18.5 and SQLAlchemy 2.1.1 / Alembic 1.20.0. druks init-db applies it, and the table has one CHECK on each verdict column. A second makemigrations comes out empty.
  • A throwaway check with the Druks pytest plugin (druks_db, druks_client; not committed, because this repo has no tests) covered these cases:
    • str(decision) and the board key are the title
    • all() and the board list the newest decision first
    • filter() with no fields raises
    • get(id=str(id)) finds the row, get_or_none(id="nope") returns None, and a miss raises ObjectNotFound
    • the save methods persist
    • the outcome column refuses maybe
    • the list route returns the new summary shape
    • the board serves its empty state and a subject_status with the run's status for each decision
    • the decision page serves one subject_status and no subject link of its own
    • a missing decision is a 404 on record_outcome and an empty state with a back link on its page

@czpython
czpython force-pushed the commonzenpython/adopt-druks-model-surface branch from d2221e0 to 870e47a Compare September 27, 2026 16:54
Druks now gives an app row a Model base. A StoredSubject gets its table,
timestamps, board, and 404. A subject's __str__ is its name, and a model
declares its order on the class line. Decision keeps only what is its own.

- Decision drops its table name, created_at, create(), get(),
  get_summary(), list_summaries(), and list_recent(). The table is now
  panel_decision. updated_at stamps itself.
- Decision names itself with its title, so runs, Activity, and the board
  show the title. It declares newest-first ordering, which the board and
  all() follow.
- The Decisions page and the list route read every decision with all().
- recommendation and outcome are DecisionAction columns. The database
  refuses any other value.
- DecisionSummary is a plain schema for the list route, which returns
  the rows.
- The outcome route and the decision page read the row with get(id=...).
  A missing decision is a 404 on the route. On the page, it is an empty
  state that links back to Decisions.
- The save methods write through save().
- One regenerated revision creates panel_decision. No install holds Panel
  data, so the old revision goes.
- Require druks>=0.8.0, the first release with this surface.
@czpython
czpython force-pushed the commonzenpython/adopt-druks-model-surface branch from 870e47a to 9c44ab9 Compare September 27, 2026 18:29
@czpython
czpython marked this pull request as ready for review September 27, 2026 18:29
@czpython
czpython merged commit be6279c into main Sep 27, 2026
@czpython czpython changed the title Read and write decisions through the Druks Model surface Catch up with the Druks Model and page surface Sep 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant