Skip to content

feat(rm-handlers): D1 — issue create form (POST-redirect-GET)#22

Merged
AdaWorldAPI merged 1 commit into
mainfrom
claude/issues-create-form-d1
Jun 23, 2026
Merged

feat(rm-handlers): D1 — issue create form (POST-redirect-GET)#22
AdaWorldAPI merged 1 commit into
mainfrom
claude/issues-create-form-d1

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

What

The depth follow-on to #21's D2 chrome on the same /issues surface — closes the most-iconic Redmine flow:

list page  →  "New issue" link  →  form (Subject + Description)  →  submit  →  detail page
   /issues       /issues/new           Subject required, ≤255           303         /issues/<id>

New sibling module crates/rm-handlers/src/issues_form.rs:

GET  /issues/new   — render an empty form (clean state)
POST /issues       — accept submission, validate, persist, redirect-or-error

Per Plan §8 file ownership, the D-track form lives in a sibling to issues.rs so the W1 list/detail file stays the merge-conflict boundary for its own width. rm-server merges both routers.

Behavior

input response
valid subject (trimmed) 303 See Other/issues/<id> (POST-redirect-GET)
empty subject 200 + form re-rendered + inline error Subject is required.
whitespace-only subject same as empty (trimmed first)
subject > 255 chars 200 + form re-rendered + inline error Subject must be 255 characters or fewer.
description empty after trim persisted as None in NewIssue
description with content trimmed + persisted as Some(String)

User's input is preserved on validation failure (no retyping). The kit HTML-escapes the echoed values on re-render — XSS-safe by construction.

#99 trap-spellbook pre-flight (5 questions cleared)

Q answer
Q1 reading FROM nothing — pure form handler (no producer reading)
Q2 lifecycle vocab None
Q3 target IR The NewIssue store row (already canonical) + the kit's FormSource
Q4 arrow direction HTTP form → store, no SurrealQL spine substitution anywhere
Q5 inverse recover N/A

Zero diagnostic signatures present.

Out of scope (named, gated)

  • Edit (PATCH /issues/:id) — D1.2 follow-on; the kit's FormSource.record_id is the seam already present.
  • CSRF tokencsrf_token: "" for now; rm-auth integration is its own follow-on (rolling token enforcement applies once the auth infra is shared).
  • Markdown preview + attachments — D5 / D6 follow-ons.
  • Status / priority / tracker / assignee fields — the FK columns wait for W2/W3 taxonomy + W4 actor to land them on IssueRow. When they do, the form grows one input per FK; no chrome change.
  • The "New issue" link on the /issues list page — one-line follow-on (literally <a href="/issues/new">New issue</a> in the chrome).

Build

Cross-refs

The depth follow-on to the D2 list chrome on the same /issues surface.
17 years of Redmine taught users one flow: list page → "New issue" link →
form with Subject (required) + Description (optional) → submit → land on
the new issue's detail page. This module ships that.

New sibling module `crates/rm-handlers/src/issues_form.rs`:

  GET  /issues/new   — render an empty form
  POST /issues       — accept submission, validate, persist, 303 to
                       /issues/<id> on success; re-render with inline
                       errors on validation failure

Per Plan §8 file ownership, the D-track form work lives in this sibling
so the W1 list/detail file stays the merge-conflict boundary for its own
width. The two routers compose at the workspace level — `rm-server`
merges issues::router + issues_form::router.

Validation:
  - subject required (trimmed, whitespace-only rejected)
  - subject ≤ 255 chars (Redmine's column cap; fail at the form
    boundary rather than DB-side opacity)
  - description trimmed; empty → None in NewIssue

On validation failure the form re-renders WITH the user's input
preserved (HTML-escaped through the kit) and an error block above the
fields. POST-redirect-GET on success so refreshing the new issue's page
doesn't re-submit.

Out of scope (named, gated): edit (PATCH /issues/:id, D1.2), CSRF token
(rm-auth integration), Markdown preview, attachments, status/priority/
tracker/assignee fields (wait for the FK columns to land on IssueRow
via W2/W3 taxonomy + W4 actor — the form grows one input per FK without
chrome change).

Tests: 14 new — 6 unit on the pure `validate()` helper, 8 integration
covering GET empty render, POST 303 + Location, POST trims subject,
POST empty desc, POST empty subject error, POST whitespace-only subject,
POST overlong subject, POST XSS payload escaped on re-render.

cargo +1.95 test -p rm-handlers --lib: 89 passed (was 75; +14). fmt +
clippy --all-targets -- -D warnings clean.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@AdaWorldAPI AdaWorldAPI merged commit dd08839 into main Jun 23, 2026
1 check passed
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.

2 participants