feat(positions): tabbed position modal, terminal slots staged into create - #199
Merged
Conversation
…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>
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.
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:
PositionCreateDtogains aTerminalslist.StageCreateTerminalClientplus itsTerminalEnrollmentstream into the same Marten session that already holds the position and grant streams.SaveChangesAsynccommits 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 buildclean ·vue-tsc --buildclean.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 400Terminal.TerminalPolicyDisabledand leaves neither position nor client behind.docs/admin/positions.mdwas updated in step.🤖 Generated with Claude Code