Skip to content

feat(rm-handlers): home overview + master layout + create forms (project/news/wiki) & CTAs#24

Merged
AdaWorldAPI merged 5 commits into
mainfrom
claude/remine-rs-upload-elv1ca
Jul 1, 2026
Merged

feat(rm-handlers): home overview + master layout + create forms (project/news/wiki) & CTAs#24
AdaWorldAPI merged 5 commits into
mainfrom
claude/remine-rs-upload-elv1ca

Conversation

@AdaWorldAPI

Copy link
Copy Markdown
Owner

Summary

POC-polish pass that makes the demo navigable end-to-end and rounds out the "New X" create forms across the seeded resources. Five commits on top of main (56da8d1), touching only rm-handlers and rm-server.

Commits

  1. home overview page + "New issue" CTA on /issues — cross-resource overview at / (rm_handlers::home) listing every seeded concept (Issues, Projects, Time entries, News, Wiki, Users, Roles, Repositories, Issue statuses, Trackers, Priorities, Custom queries, Relations) with live row counts and links to each list route, plus a "New issue" quick action. Pure render_home() split from the handler for unit testing; counts come from each Store::list_*().len(). Adds generic render_action_bar(&[(label, href)]) (Redmine's .contextual strip, HTML-escaped) reused by W2/W3/detail pages.
  2. Redmine-shaped master layout — global top menu / master chrome.
  3. Project create form + "New project" CTA.
  4. News create form + factor render_errors to common.
  5. Wiki create form + "New wiki page" CTA.

Changes

crates/rm-handlers/src/common.rs        | 100 +++-
crates/rm-handlers/src/home.rs          | 230 +++++++++  (new)
crates/rm-handlers/src/issues.rs        |  28 +-
crates/rm-handlers/src/issues_form.rs   |  22 +-
crates/rm-handlers/src/lib.rs           |   4 +
crates/rm-handlers/src/list_chrome.rs   |  57 +++  (new)
crates/rm-handlers/src/news.rs          |  23 +-
crates/rm-handlers/src/news_form.rs     | 320 +++++++++++  (new)
crates/rm-handlers/src/projects.rs      |  29 +-
crates/rm-handlers/src/projects_form.rs | 389 ++++++++++++  (new)
crates/rm-handlers/src/wiki_form.rs     | 278 +++++++++  (new)
crates/rm-handlers/src/wiki_pages.rs    |  23 +-
crates/rm-server/src/router.rs          |   9 +-
13 files changed, 1474 insertions(+), 38 deletions(-)

rm-server: home::router is mounted at / in build_router_with, replacing the storeless proof-of-shape index (which stays on the scaffolding build_router()).

Notes

  • Counts are list_*().len() today and become COUNT(*) when a SQL backend lands — handler shape unchanged.
  • Per the author's commit trailers, the CI gate was green locally on toolchain 1.95 (cargo fmt --all --check, clippy --all-targets --workspace -D warnings, test --workspace), with new unit tests for the added render functions.

🤖 Generated with Claude Code


Generated by Claude Code

claude added 5 commits June 30, 2026 19:42
The two deferred POC-polish items that make the demo navigable
end-to-end without hand-typing URLs.

- home: new cross-resource overview at `/` (rm_handlers::home). Lists
  every seeded concept (Issues, Projects, Time entries, News, Wiki,
  Users, Roles, Repositories, Issue statuses, Trackers, Priorities,
  Custom queries, Relations) with its live row count and a link to the
  resource's list route, plus a "New issue" quick action. Pure
  render_home() is split from the handler so the markup is unit-tested
  without booting a store; counts come from each Store::list_*().len()
  (becomes COUNT(*) when a SQL backend lands — handler shape unchanged).
- list_chrome: generic render_action_bar(&[(label, href)]) — Redmine's
  top-right `.contextual` action strip. HTML-escapes label + href; an
  empty slice yields no stray <nav>. W2/W3/detail pages reuse it.
- issues: wire the action bar into the list with a "New issue" link to
  the D1 create form (/issues/new).
- rm-server: mount home::router at `/` in build_router_with, replacing
  the storeless proof-of-shape index (which stays on the scaffolding
  build_router()).

CI gate green locally on toolchain 1.95: cargo fmt --all --check,
clippy --all-targets --workspace -D warnings, test --workspace
(6 new tests, all green).
wrap_in_doc now emits the master-layout skeleton every page shares:

- #top-menu — persistent global nav (Home, Projects, Issues, Spent
  time, News, Wiki) as a const, spliced verbatim (no per-request alloc
  beyond the outer format!).
- #header — app title linking home.
- #main > #content — the fragment wrapper.
- viewport meta for the eventual responsive stylesheet.

The id/class hooks mirror Redmine's own markup so G1's base.askama
template + stylesheet drop in cosmetically over this shell. Title stays
HTML-escaped (XSS guard, codex P1 on PR #10). All existing page-body
assertions are substring checks, so the added chrome doesn't break them.

CI gate green on toolchain 1.95: fmt --all --check, clippy --all-targets
--workspace -D warnings, test --workspace.
D1 create flow for Project, mirroring issues_form one resource over.

- projects_form: GET /projects/new + POST /projects. Name (required,
  <=255) + Identifier (required, <=100, Redmine slug rule: starts with a
  lowercase letter, then [a-z0-9_-]). 303 to /projects/<identifier> on
  success (POST-redirect-GET); 200 re-render with inline errors on
  failure, input preserved. Pure validate() + is_valid_identifier() are
  fully unit-tested.
- projects: "+ New project" contextual action on the list -> /projects/new.
- rm-server: merge projects_form::router. The static /projects/new
  coexists with /projects/:identifier — matchit gives the static segment
  priority, same proven shape as /issues/new vs /issues/:id.

CI gate green on toolchain 1.95: fmt --all --check, clippy --all-targets
--workspace -D warnings, test --workspace.
News is the third form module, so per the codebase's "factor on the
third caller" rule (Plan §1.6) the duplicated form-error block lifts into
common::render_errors (now pub(crate)); issues_form + projects_form drop
their copies and import it.

- news_form: GET /news/new + POST /news. Title (required, <=255) +
  Summary (optional, <=255) + Description (required), mirroring Redmine's
  News validations. 303 to /news/<id> on success (record-keyed like
  Issue); 200 re-render with inline errors on failure. Pure validate()
  unit-tested.
- news: "Add news" contextual action on the list -> /news/new.
- common: render_errors moved here + directly unit-tested.
- rm-server: merge news_form::router.

CI gate green on toolchain 1.95: fmt --all --check, clippy --all-targets
--workspace -D warnings, test --workspace.
Fourth create form, completing the "create any core resource" set
(Issue, Project, News, Wiki) on the shared form chrome.

- wiki_form: GET /wiki/new + POST /wiki. Title (required, <=255 — it's
  the URL slug) + Body (optional — a stub page is valid). 303 to
  /wiki/<title> on success (title URL-encoded via encode_path_segment,
  so spaces work); 200 re-render with inline errors on failure.
- wiki_pages: "New wiki page" contextual action on the list -> /wiki/new.
- rm-server: merge wiki_form::router.

Documented MVP routing caveat: /wiki/new shadows a page literally titled
"new" (static beats /wiki/:title); nested /projects/:id/wiki/new lands
with the project sidebar follow-up.

CI gate green on toolchain 1.95: fmt --all --check, clippy --all-targets
--workspace -D warnings, test --workspace.
@AdaWorldAPI AdaWorldAPI merged commit 386ef01 into main Jul 1, 2026
1 check passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: cd2b270cef

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

name: form.name.trim().to_string(),
identifier: form.identifier.trim().to_string(),
};
let project = state.store.create_project(new).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject duplicate project identifiers before creating

When a user submits an identifier that already exists, this path still inserts a new project because Store::create_project currently just issues CREATE project and its docs note uniqueness conflicts only once an index lands; the detail route then looks up by identifier with LIMIT 1, so the 303 to /projects/<identifier> can show the older project and both rows share the same URL. Please check for an existing identifier and re-render the form with a validation error before calling create.

Useful? React with 👍 / 👎.

title: form.title.trim().to_string(),
body: form.body.trim().to_string(),
};
let page = state.store.create_wiki_page(new).await?;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject duplicate wiki titles before redirecting

When someone submits a wiki title that already exists, this creates a second wiki_page row even though the title is the URL key; the list links both rows to the same /wiki/<title> URL and the detail lookup uses the title with LIMIT 1, so the redirect can land on the existing page instead of the one just created. Please validate that the title is unused and show an inline error before inserting.

Useful? React with 👍 / 👎.

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