Skip to content

feat(positions): tabbed position modal, terminal slots staged into create - #199

Merged
windischb merged 1 commit into
developfrom
feat/position-modal-contract
Aug 15, 2026
Merged

feat(positions): tabbed position modal, terminal slots staged into create#199
windischb merged 1 commit into
developfrom
feat/position-modal-contract

Conversation

@windischb

@windischb windischb commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

Fixes the two places where the Positions modal broke the admin Modal & Form Contract (Atlas engineering/modal-form-contract, AGREED 2026-07-24).

1. Tabs

Five stacked sections become four tabs — General / Terminals / Authorized users / Staffing sessions — the shape every comparable modal already uses (Group, User, Client, App, LoginProvider); only the small three-section modals (Scope, Realm) stack sections.

Per rule 5 the sessions tab is absent in create: staffing sessions are runtime records that cannot exist before the position. Since rule 1 warns that "a setting on another tab is as unfindable as one behind a toggle", each tab label carries a warning popover listing that tab's validation issues, so a disabled Save always has a visible cause. Section headings that merely repeated the new tab label were dropped.

2. A position is creatable as a whole

Terminal slots used to be edit-only — the create dialog said "Terminal slots can be set up after the position is created." Grants in the very same modal were already staged, which is rule 3 (same relationship → same behavior) broken inside one component.

Slots are now staged in create and committed by the single Save, exactly like the service account's initial credential:

  • PositionCreateDto gains a Terminals list.
  • The create endpoint validates the staged slots up front (terminal use must be enabled in this same save; display name required), then stages each slot's terminal-managed OAuth client via the existing StageCreateTerminalClient plus its TerminalEnrollment stream into the same Marten session that already holds the position and grant streams.
  • One SaveChangesAsync commits everything. A rejected slot returns before that call, so there is no half-created position and no orphaned OAuth client.

Enrollment stays a later step on purpose — that is a ceremony on the device, not a setting.

Verification

  • dotnet build clean · vue-tsc --build clean.
  • dotnet test --filter ~Positions: 56/56 green, including two new tests: two staged slots come back as Pending with their managed clients linked to position and enrollment; a staged slot without terminal use returns 400 Terminal.TerminalPolicyDisabled and leaves neither position nor client behind.
  • The full suite is left to CI: the local run was started before the machine slept overnight (314 min wall clock, 137 s CPU — it simply stood still) and was killed rather than resumed. No indication of a problem in the suite itself.
  • The admin SPA sits behind a login, so the tabs were not clicked through in a browser; the layout change is covered by type-checking and review, the create round-trip by the two integration tests above.

docs/admin/positions.md was updated in step.

🤖 Generated with Claude Code

…eate

Brings PositionDetails in line with the admin Modal & Form Contract on the
two points it broke.

Tabs: the five stacked sections become General / Terminals / Authorized
users / Staffing sessions, the shape every comparable modal already uses
(Group, User, Client, App, LoginProvider). Sessions is absent in create —
it cannot exist before the position does. Since a tab can hide a field,
tab labels carry a warning popover listing that tab's validation issues,
so a disabled Save always has a visible cause. Section headings that only
repeated the tab label are gone.

Whole-entity create: terminal slots are now staged in create mode and
committed by the single Save, the way grants already were and the way the
service account stages its initial credential. PositionCreateDto grows a
Terminals list; the create endpoint validates it up front, then stages
each slot's terminal-managed client via StageCreateTerminalClient plus its
enrollment stream into the same Marten session that holds the position and
grant streams. One SaveChangesAsync commits all of it, and a rejected slot
returns before that — no half-created position, no orphaned OAuth client.
Enrollment deliberately stays a later step: it is a ceremony on the device,
not a setting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@windischb
windischb enabled auto-merge (squash) August 15, 2026 04:56
@windischb
windischb merged commit d0bbe4e into develop Aug 15, 2026
8 checks passed
@windischb
windischb deleted the feat/position-modal-contract branch August 15, 2026 05:12
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.

1 participant