Catch up with the Druks Model and page surface - #7
Merged
Merged
Conversation
czpython
force-pushed
the
commonzenpython/adopt-druks-model-surface
branch
from
September 27, 2026 16:54
d2221e0 to
870e47a
Compare
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
force-pushed
the
commonzenpython/adopt-druks-model-surface
branch
from
September 27, 2026 18:29
870e47a to
9c44ab9
Compare
czpython
marked this pull request as ready for review
September 27, 2026 18:29
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Druks now gives app rows a
Modelbase withget,get_or_none,filter, andall(czpython/druks#736, czpython/druks#739). AStoredSubjectgets 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 aLiteralcolumn is stored under a CHECK (czpython/druks#738). A page shows where the work stands withui.SubjectStatus, anddruks check-appchecks an app with no database (czpython/druks#744). Panel moves to that surface.Decisionand its pages keep only what is their own.Model
Decisiondrops__tablename__,created_at,create(),get(),get_summary(),list_summaries(), andlist_recent(). Druks now supplies each of these. The table ispanel_decision.updated_atstamps itself.__str__returns the title, so runs, Activity, and the board show the decision's title, notdecision 7.ordering=("-created_at", "-id")lists the newest decision first on the board and inall().GET /api/panel/decisionsread withDecision.all(). They list every decision, not only the newest 100. This matches the page's description: "Every question this panel has weighed."recommendationandoutcomeareMapped[DecisionAction | None]. The database refuses any value exceptproceed,revise, orpass.DecisionSummaryis a plainSchema(id,title,question,recommendation,outcome,createdAt). The list route returns the rows, and the response model serializes them. The response no longer carrieskey.save_panel()andsave_outcome()write throughsave().mapped_column(JSONB)because they are typedlist[dict[str, Any]]anddict[str, Any] | None, which the Druks docs name this way.Pages and routes
record_outcomeand the decision page read the row withDecision.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.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'sget_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.empty=ui.EmptyState(...)in place ofempty_text, which Druks removed. The decision link on each row takes its words from the title.Migration and dependencies
panel_0001createspanel_decisionwith one CHECK on each verdict column. No install holds Panel data, so the old revision is deleted, not migrated.uv run druks check-app panelas a gate.druks>=0.8.0.Before you merge
uv.lockstill pins 0.6.0 becausedruks>=0.8.0does not resolve yet. Runuv lockand add the lockfile change to this commit.panel_0001must be re-initialized. Droppanel_decisionsandalembic_version_panel, then rundruks 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, anddruks check-app panel: cleandruks makemigrations panelagainst 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-dbapplies it, and the table has one CHECK on each verdict column. A secondmakemigrationscomes out empty.druks_db,druks_client; not committed, because this repo has no tests) covered these cases:str(decision)and the board key are the titleall()and the board list the newest decision firstfilter()with no fields raisesget(id=str(id))finds the row,get_or_none(id="nope")returns None, and a miss raisesObjectNotFoundmaybesubject_statuswith the run's status for each decisionsubject_statusand no subject link of its ownrecord_outcomeand an empty state with a back link on its page