Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-281Done

5.3 Custom fields (per-project definitions)

The extensible-schema layer: project admins define custom fieldstext · number · date · select (single, with managed options) · user — in the project settings area (6.4), and issues carry values for them, rendered and inline-edited on the detail rail beside the built-in fields. Values are stored typed-EAV (one row per issue × field with per-type value columns — the shape Jira itself uses) so Epic 6 can filter on them with real predicates and indexes — the stub's "design carefully" constraint, honoured in the schema, not deferred.

The Jira-verified shape (rung 1, team-managed, checked at plan time). Our five types each map to a verified member of Jira's team-managed set (Short text / Number / Date / Dropdown / People). Team-managed Dropdown is single-select only (multi-select is the separate Checkbox type), so single-select IS the mirror shape. Definitions live at Project settings → Fields, project-admin-gated (the 6.4 two-tier check), project-scoped, with the documented Jira caps adopted as guards: 50 fields per project, 55 options per field. On the issue view, fields WITH values render as rail cards; empty ones collapse behind "Show more fields" (Jira's hide-when-empty rule) — mirrored without the work-type layout-config subsystem (layouts / per-type required / create-form placement are the documented extension, composing into 6.5's settings hub). Field deletion is hard (team-managed has no trash: immediate, permanent, values destroyed — confirm names the value count). Options rename and reorder freely; an in-use option archives (hidden from new selection, existing values keep rendering with an archived mark) and deletes only when unused — the verified "Optimize" rule; in-use-delete semantics are undocumented in the mirror, so the safe rule wins. Value changes write the 1.4.6 revision diff (customFields.<key>) — the History entries Story 5.5 renders, same as built-in fields.

Validation is the service's job, per type: number → decimal (the storage column, not float drift); date → date-only ISO, UTC-safe (the dueDate convention); select → the option must belong to the field and (for NEW sets) not be archived; user → a workspace member who can view the project (the 6.4 assignableMembersService scoping — the same rule as assignee/mentions); text → length-capped. Setting a value upserts the row; clearing deletes it (no tombstone rows). Who edits values = who edits the issue (admin/member; read-only viewer sees values, no editors).

Bounded everywhere (finding #57). The detail read joins ≤50 definitions + this issue's value rows (one bounded query slotted into getIssueDetail's parallel fetch); the admin list is ≤50 by the cap; options ≤55. The Epic-6 contract is documented in the schema subtask: per-type value columns indexed by [fieldId, value*] so 6.1's filter builder compiles JOIN-on-value predicates without a schema change.

Completeness — the real-product states. Admin: empty ("No custom fields yet"), the caps reached (50/55 — disabled add + explanatory copy), delete confirm with value count, option archive vs delete split, loading/error. Rail: empty-value placeholder, "Show more fields" expander, per-type editors with inline validation errors, archived-option rendering, viewer read-only, concurrent-edit refresh (the rail's existing optimistic-concurrency pattern). All drawn by 5.3.4/5.3.5, asserted in 5.3.8.

Out of scope (documented extension slots, each justified): the remaining mirror types (paragraph / checkbox-multi / labels / multi-person / formula / time stamp — additive types on the same EAV substrate; labels overlap Story 5.4's taxonomy); work-type field layouts, per-type required flags, and create/edit-form placement (the layout-config admin subsystem — 6.5 composes the settings hub; values are editable the moment an issue exists via the rail); custom fields in the board/list/tree columns (Epic-6 saved-views territory); cross-project/global fields (company-managed Jira, not the team-managed mirror); and the filter UI over values (Story 6.1, which consumes the documented predicate contract).

Verification

  • Pull the Story branch, pnpm install, pnpm prisma migrate dev (applies the 5.3.1 definition/option/value migration cleanly; re-run reports "No difference detected"), pnpm db:seed, pnpm dev.
  • pnpm test:coverage — Vitest (real Postgres) over the definitions + values services (CRUD, caps, per-type validation, option archive/delete rules, permission gates) ≥90% per-file branch/fn/line; new repo methods carry empty-input-guard tests.
  • Admin flow: sign in as zhuyue@motir.co / !QAZ1qaz (project admin) → Project settings → Fields (matching design/projects/fields.mock.html). Create one field of each type (Severity/select with 3 options, Customer/text, Effort/number, Go-live/date, Stakeholder/user) → all list in order; rename + reorder a select option; archive an in-use option (stays rendered on issues, gone from new pickers); delete an unused option; delete a field → the confirm names the value count and values vanish. As eikooc@motir.co (member, not project admin) → the Fields page is read-only/forbidden.
  • Rail flow: open an issue → defined fields with values render as rail cards below the built-ins (matching design/work-items/custom-fields.mock.html); empty ones sit behind "Show more fields"; set each type inline (text, number, date via DatePicker, select via Combobox, user via the member picker) → values persist and the revision trail records customFields.<key> diffs; clear a value → the card returns to the empty set; invalid input (bad number, archived option, non-member user) errors inline, nothing persists.
  • Permissions: a project viewer sees values but no editors; cross-workspace reads 404 (finding #44).
  • Caps: the 51st field and 56th option are rejected with the explanatory error; the admin UI disables add at the cap.
  • Epic-6 seam: the schema doc note exists (typed value columns + [fieldId, value*] indexes + the JOIN predicate sketch); a raw SQL spot-check filters issues by Severity = High using only the indexes (no JSON parsing).
  • pnpm test:e2e --grep custom-fields — Playwright over the real stack: define → set on an issue → edit → History shows the change → delete field → values gone.
  • a11y check: the Fields admin page + the rail editors pass the strict axe sweep (labelled controls, keyboard-complete pickers, "Show more fields" as a disclosure, state as text); colour via --el-*, shape via element shape tokens.