Skip to content

Stage E: split the mixed System Settings card, add the app role roster - #1316

Merged
Paul Lizer (paullizer) merged 2 commits into
feature/admin-settings-iafrom
admin-ia-stage-e
Aug 19, 2026
Merged

Stage E: split the mixed System Settings card, add the app role roster#1316
Paul Lizer (paullizer) merged 2 commits into
feature/admin-settings-iafrom
admin-ia-stage-e

Conversation

@paullizer

Copy link
Copy Markdown
Contributor

Stage E, the last content change in the Admin Settings rework. Splits the mixed System Settings card and adds the app role roster.

Base: feature/admin-settings-ia. Stacked on #1315.

The mixed card is gone

system-settings-section put five unrelated concerns under one heading. Each moved to the tab that owns it, with the field markup carried across byte-for-byte:

Field New home
max_file_size_mb Workspaces → Files & Sharing
conversation_history_limit Chat → Chat Experience
default_system_prompt Chat → Chat Experience
access_denied_message Security → Access & Roles
idle timeout (4 fields) stays in Security → Session

What remains in Session is only the idle timeout, so the card is renamed idle-timeout-section for what it actually holds. Security → Session was an interim home from #1311; it is now honest.

Splitting a card changes card ids but not field names, which is why the field contract passed unchanged. Card ids are structural and move freely during this work; field names are the breaking surface, and the contract test tracks only those — deliberately.

App Role Requirements roster

Ten settings across seven tabs can each require an Entra app role. You could not read the overall access policy without hunting through the whole of Admin Settings.

Security → Access & Roles now lists every one, with a switch and a link back to the setting in its own tab.

Two things make this safe and durable:

  • Built from the page, not from a list. The roster queries input[name^="require_member_of_"] at runtime, so a new role requirement added anywhere appears automatically and this can never fall out of step.
  • Mirrors carry no name attribute, following the existing proxy convention, so each setting is still posted exactly once. Sync is two-way — the mirror drives the canonical input and dispatches a change event, and the canonical input updates the mirror when changed on its own tab.

Verified against a simulated DOM:

rows built: 2
labels    : Require CreateGroups App Role | Require WorkflowUser App Role
links     : In Group Workspaces | In Workflow
mirrors carry a name attr: false

mirror on  -> real=true
real off   -> mirror=false

A structural guard worth keeping

test_every_pane_partial_is_balanced checks <div> and <section> balance in every pane.

An unbalanced pane does not fail to render. It silently nests the panes that follow it, so they get hidden and the failure surfaces somewhere completely unrelated. That happened here: a stray </div> in access-roles.html made a Send Feedback test fail. The new test names the offending file directly:

E   jobs.html: 13 <div> vs 12 </div>

Verified against a deliberately broken pane.

The parity test also earned its keep, catching the new roster listed in the nav map in a different order from the markup. Nav order must match pane order.

Result

Navigation is now 14 groups / 44 tabs / 93 sections, from 17 flat tabs.

Check Result
Field contract 462 names, byte-identical
Regression set (75 files) 32 failures, identical set to baseline
Jinja compile 46/46 admin templates
Pane balance all panes balanced
XSS sinks pass

Next

Only cleanup remains: revert the six CI branch filters added so staging PRs would run the gates, then open the staging → Development PR.

Paul Lizer (paullizer) and others added 2 commits August 19, 2026 17:09
One tab carried the entire backup, migration, restore, Cosmos editing
and job history surface: 1,622 lines in a single scroll.

  Backup          readiness, backup, schedule, storage, encryption
  Migrate         migration workflow
  Restore         backup inventory and restore
  Cosmos Editor   direct database editor
  Jobs            job history

Navigation is now 14 groups / 44 tabs / 88 sections. Stage D is done,
from an original 17 flat tabs.

Three problems the existing tools could not handle

  The migration card is a <section>, not a <div>, so every div
  balancing helper walked straight past it and the tools reported five
  top level cards where there were six. Its boundaries were found by
  balancing <section> instead, and the split was driven from explicit
  ranges with a check that every one of the 1,183 lines lands in
  exactly one output.

  Eleven dialogs, six of them opened from JavaScript rather than a
  button, serving what became five different tabs. A dialog inside a
  tab pane cannot appear while another tab is showing, so all eleven
  moved outside the panes. Checked first that none carried a name=
  attribute: the pane's twelve form fields are all radio groups inside
  the migration card, which stayed put.

  One save button, one status line and one operational warning serve
  all five tabs.

Group-shared regions

  Shared controls cannot be copied into each pane, because that repeats
  element ids and the JavaScript module would bind to the wrong one,
  and cannot live in one pane, because an inactive pane is hidden and
  the other four tabs would lose the save button.

  They now sit outside the panes marked
  data-admin-group-shared="backup-recovery" and are revealed only while
  that group is active, synced from every path that activates a tab
  including Bootstrap's own shown.bs.tab event.

  syncAdminGroupSharedRegions resolves the owning group from either
  navigation. Reading only the top tab strip was a real bug: that strip
  is not rendered at all in the sidebar layout, so the save button
  would have been hidden permanently. Verified against a simulated DOM
  in both layouts.

Verified
  field contract     462 names / 110 card ids, unchanged
  regression set     32 failures, identical to baseline
  jinja compile      46/46 admin templates
  xss sinks          pass
  element ids        all 1,608 ids in the composed template are unique
  modal placement    every dialog reachable from its trigger

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
system-settings-section mixed five unrelated concerns under one
heading. Each moved to the tab that owns it, with the field markup
carried across byte for byte.

  max_file_size_mb            -> Workspaces / Files & Sharing
  conversation_history_limit  -> Chat / Chat Experience
  default_system_prompt       -> Chat / Chat Experience
  access_denied_message       -> Security / Access & Roles
  idle timeout (4 fields)     stays in Security / Session, and the card
                              is renamed idle-timeout-section for what
                              it now holds

Splitting a card changes card ids but not field names, which is why
the field contract passed unchanged. Card ids are structural; field
names are the breaking surface, and the contract test tracks only
those, deliberately.

App Role Requirements roster

  Ten settings across seven tabs can each require an Entra app role,
  so the overall access policy could not be read without hunting
  through the whole of Admin Settings. Access & Roles now lists every
  one of them.

  The roster is built at runtime from
  input[name^="require_member_of_"] rather than from a list, so a new
  role requirement added anywhere appears automatically and this can
  never fall out of step.

  Each row is a mirror carrying no name attribute, following the
  existing proxy convention, so the setting is still posted exactly
  once. Sync is two way: the mirror drives the canonical input and
  dispatches a change event, and the canonical input updates the
  mirror when changed on its own tab. Verified against a simulated
  DOM.

A structural guard worth keeping

  test_every_pane_partial_is_balanced checks <div> and <section>
  balance in every pane. An unbalanced pane does not fail to render,
  it silently nests the panes that follow it, so the failure surfaces
  somewhere unrelated. Found the hard way here: a stray </div> in
  access-roles.html made a Send Feedback test fail. The new test names
  the offending file directly, and is verified against a deliberately
  broken pane.

The parity test also caught the new roster being listed in the nav map
in a different order from the markup. Nav order must match pane order.

Navigation is now 14 groups / 44 tabs / 93 sections.

Verified
  field contract   462 names, unchanged
  regression set   32 failures, identical to baseline
  jinja compile    46/46 admin templates
  pane balance     all panes balanced
  xss sinks        pass

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@paullizer
Paul Lizer (paullizer) merged commit 6750ca5 into feature/admin-settings-ia Aug 19, 2026
11 checks 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.

1 participant