DRU-390 - Issues: UI pages: board, list, ticket - #412
Draft
druks-operator-treadstone[bot] wants to merge 3 commits into
Draft
DRU-390 - Issues: UI pages: board, list, ticket#412druks-operator-treadstone[bot] wants to merge 3 commits into
druks-operator-treadstone[bot] wants to merge 3 commits into
Conversation
Co-authored-by: Cursor <cursoragent@cursor.com>
routes.py carries the operations pages, the dashboard, and the sandbox all call. set_status is the one door that moves a ticket and the only one that publishes ticket.transitioned — skipped when the row is already there, so a repeat does not open a second build. Content edits and creates stay quiet. Status now says on the enum whether it is completed and whether it is terminal, which is what the funnel reads. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
chaosk
force-pushed
the
agent/DRU-389
branch
from
September 7, 2026 21:43
95ee7e8 to
67e3be6
Compare
9 tasks
chaosk
force-pushed
the
agent/DRU-389
branch
2 times, most recently
from
September 8, 2026 03:49
3611ea5 to
4f7aad3
Compare
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.
Linear ticket: DRU-390
Plan
DRU-390 — Issues app UI: board, list, ticket
Target repo is
czpython/druks, not thedruks-appscheckout in this sandbox. The rewritten ticket and operator note put this work inbackend/druks/contrib/issues/, stacked on PR #411 (branchagent/DRU-389, still open). Theissuesapp, models, enums, schemas, and theissues_*write doors all live there; none exist indruks-apps. See Q1 — the sandbox must be re-pointed at druks and branched fromagent/DRU-389before implementation.Scope: add
backend/druks/contrib/issues/pages.py(new) and setnavigation = ["board", "list"]inbackend/druks/contrib/issues/app.py. Pages only — nodist/, no JavaScript, no newdruks.uiblock. No new routes: every write already exists as anoperation_idonroutes.pyfrom #411.How pages work here (verified against
druks/uiandbackend/tests/druks-field_notes/.../pages.py):@ui.page("/")registers a top-level page auto-discovered from the package;App.navigationentries must be static top-level pages (is_static and not parent, base.py:239). Soboard(/) andlist(/list) are top-level@ui.page;ticket(/tickets/{identifier}) is a top-level parameterized page reached byui.Link(page="ticket", arguments={"identifier": ...}). Exactly one page at/.ui.Pageacceptsfollows=<subject>; a namedui.Section+refresh="region"ui.Actionis the live comment region (blocks.py).ui.Action.fieldsexists, so creation is an action, not a/newpage.Three pages (read models directly; call doors only for writes):
board/— oneui.Columns, aui.Sectionper status Backlog, Todo, Ready for Agent, In Progress, In Review, Done (Cancelled omitted), each holdingui.Cards grouped in Python from a singleTicket.list_board(); empty status →ui.EmptyState(e.g.ui.Cards(empty=...)). Card title = title, description = identifier + priority/assignee when set, controlui.Links toticket. Page-level New ticket / New project controls, shown on an empty install.list/list— oneui.Table(identifier, title, priority, assignee, project, updated) per status in order In Progress, In Review, Ready for Agent, Todo, Backlog, Done, Cancelled, rows fromTicket.list_for_status(status)(alreadyupdated_at desc).ticket/tickets/{identifier}—ui.Page(follows=<ticket>); title,ui.Markdowndescription,ui.Facts(status/priority/assignee/project/identifier); editui.Form→issues_update_ticket; moveui.Actionwith a statusui.SelectField→issues_set_status; a named commentsui.Sectionwhose commentui.Form/ui.Action(required body,issues_add_comment,refresh="region") appends in place; empty thread →ui.EmptyState. Unknown identifier → aui.Pagewithui.EmptyState.Options / lookups: project
SelectFieldfromProject.list()(label=name, value=str(id)); assignee fromAccount.list_non_system()usernames (value=account id) + one Unassigned choice; resolveproject_id/assignee_idto names for cards & table via those two lists. New-ticket status select defaults toStatus.TODO. After a create, the action refreshes the board (refresh="page") — the new identifier is minted server-side and anAction.linkis static, so a link-to-new-ticket isn't expressible.Risk — Unassigned wiring:
SelectField.valueis a plain string;create_ticket/update_ticketon #411 treatassignee_id=""as a real id (require_assignee→ 404). Implementer must confirm the shell drops empty select values before the POST; if it does not, the minimal blank→Nonecoercion belongs on those two doors (a stacked-branch tweak, not a page hack). Do not invent a sentinel.Acceptance Criteria
backend/druks/contrib/issues/pages.pyis added and declares exactly three top-level pages via@ui.page:boardat/,listat/list, andticketat/tickets/{identifier};backend/druks/contrib/issues/app.pysetsnavigation = ["board", "list"].@ui.page(...)decorators and their function names; grep app.py for the navigation assignment.boardreturns a singleui.Columnsholding oneui.Sectionper status in order Backlog, Todo, Ready for Agent, In Progress, In Review, Done (Cancelled absent); cards are built from aTicket.list_board()read grouped by status, each card's title = the ticket title and description carries the identifier plus priority/assignee when set, and the card controlui.Links to theticketpage witharguments={"identifier": ...}; an empty status renders aui.EmptyStaterather than nothing.boardin pages.py: confirm the status-order list excludes Cancelled, cards derive from list_board(), each card links to the ticket page by identifier, and the empty branch yields an EmptyState.listrenders oneui.Tableper status in order In Progress, In Review, Ready for Agent, Todo, Backlog, Done, Cancelled, each table with columns identifier, title, priority, assignee, project, updated and rows sourced fromTicket.list_for_status(status); Cancelled is present as the last section.listin pages.py: confirm the seven-section order, the six columns, list_for_status per section, and Cancelled last.ticketreturnsui.Page(..., follows=<the Ticket>)showing the title, aui.Markdowndescription, aui.Factsof status/priority/assignee/project/identifier, an editui.Form(fields title, description, priority, assignee, project) whose action targets operationissues_update_ticket, a moveui.Actioncarrying a statusui.SelectFieldtargetingissues_set_status, and a namedui.Sectionfor comments whose commentui.Form/ui.Actionhas a required body field, operationissues_add_comment, andrefresh="region"; an empty thread renders aui.EmptyState, and an unknown identifier returns a page with aui.EmptyStaterather than raising.ticketin pages.py: confirmfollows=is the ticket, the three operations are named, the SelectField sits on the move action,refresh="region"is inside a named Section, and the not-found branch returns an EmptyState page.Status.TODO), priority, and assignee and targetsissues_create_ticket; New project collects name and prefix and targetsissues_create_project; after a successful create each action refreshes the board (refresh="page").Project.list()with label=name and value=str(project.id); assignee select options come fromAccount.list_non_system()usernames (value=account id) plus one explicit Unassigned choice; no no-project option is offered, and card/table assignee and project columns resolve ids to names via those same two lists.dist/directory and no newdruks.uiblock class; the board is composed only fromui.Columns/ui.Section/ui.Card(/ui.Cards).dist/paths, no new class inbackend/druks/ui/, andboarduses only Columns/Section/Card/Cards.