Admin Settings information architecture rework: 17 flat tabs to 14 groups - #1319
Merged
Conversation
Admin Settings had grown to 13,526 lines in a single 1 MB template, which
made the upcoming information architecture rework impractical to do safely.
Each tab pane now lives in application/single_app/templates/admin/_panes/
and is pulled back in with {% include %}. The parent template keeps the
global form, the modals, and the script blocks, so the submitted payload is
unchanged: every field name, and all 109 configuration card ids, are
byte-identical to before the split.
Functional tests that read admin_settings.html straight from disk would now
only see the parent shell, so they route through a new shared helper,
test_support.templates, which inlines the Admin Settings partials and leaves
every other repository file untouched.
Adds test_admin_settings_template_composition.py to pin the contract: the
parent delegates to partials, no partial is orphaned, composition restores
the full card inventory, and no functional test asserts on a partial-backed
card without composing the template first.
Verified against the 75 functional test files that touch admin_settings.html:
32 pre-existing failures before and after, identical sets, zero regressions.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Latest Features was pinned first in both the top-tab strip and the admin sidebar, and its pane was hard-coded as the default active tab, so it opened on every single visit to Admin Settings. That made a curated release-notes tab behave like the landing page. It now sits last in both navigations, after Send Feedback, and General is the default landing tab instead. The Latest Features markup itself is unchanged, including its New badge and the hide/unhide dropdown. Three places drove the old behaviour and all three are updated: the top-tab button ordering and active class, the tab-pane show active class, and the sidebar bootstrap default in admin_sidebar_nav.js. Test assertions that pinned the old ordering are inverted to pin the new intent, so a future change cannot silently restore the landing behaviour. Field names and card ids remain byte-identical, and the 75 functional test files covering admin_settings.html show the same 32 pre-existing failures as before the change. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Two Admin Settings features were rendered as afterthoughts compared to every other tab. Global Identities was a bare JavaScript mount point: no heading, no description, and no intro copy, so the tab rendered as an unlabelled widget. It now has a heading and explains that identities are deployment-wide and that secrets are held in Key Vault when Key Vault storage is configured, which matches the behaviour in functions_workspace_identities.py. File Sync is one of the larger settings surfaces at roughly 400 lines and 32 inputs, but it was the only tab with no sidebar submenu, so none of its sub-areas could be reached or searched from the navigation. Its source-type and per-workspace-type areas now carry ids and are exposed as sidebar destinations. The existing data-testid hooks are left in place. The composition guard added with the template split is extended to cover form field names as well as card ids. That immediately surfaced seven more tests that read admin_settings.html without composing its partials, including ones asserting on fields rather than cards, and those are now routed through the shared helper. Field names and card ids remain byte-identical, and the 75 functional test files covering admin_settings.html show the same 32 pre-existing failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The classification banner live preview never worked. Its script sat between
{% endblock %} and {% block scripts %} in a child template, and Jinja drops
content outside blocks in a child template, so the code was never rendered.
Moving it inside the scripts block makes the preview respond to banner text,
background colour, and text colour changes.
Bumps VERSION to 0.260.004 and adds the fix write-up plus release notes for
this delivery stage: the template split, the Latest Features reposition, the
Global Identities and File Sync surfaces, and this preview fix.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
scrollToSection resolves a sidebar target with sectionMap[sectionId] || sectionId, so an entry that maps a key to itself does nothing. The map had grown to 72 entries: 66 were self-referencing no-ops, two were referenced by no sidebar link, and control-center-admin-section pointed at an element that does not exist anywhere in the template. Every new admin section had been registered twice, once as a sidebar link and once in a map that did not need it, so the map drifted out of sync with the markup without anything failing. Reduced to the six genuine aliases. A new test in the sidebar parity suite fails if a no-op, dangling, or unreferenced entry is added back, so the map cannot rot again. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Two alerts were reported against this branch. js/xss-through-dom (high) in admin_settings.html: showPreview fell back to assigning the raw editor contents to innerHTML when the Markdown editor had not initialized, so text typed into the editor was reinterpreted as HTML. The comment already stated the intent was to just show raw text, so the fallback now uses textContent, which removes the sink and matches that intent. The Markdown branch in the same function also wrote rendered HTML straight to innerHTML. It is now passed through DOMPurify, following the pattern the User Agreement preview in this same template already uses. DOMPurify comes from the locally served static bundle, so no external asset is introduced. py/unused-import in test_admin_settings_template_composition.py: resolve_template_includes was imported but only appeared as a string literal inside COMPOSITION_HELPERS, so the import was genuinely unused. Bumps VERSION to 0.260.005 with release notes and fix documentation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The previous fix assigned DOMPurify output through an intermediate variable, which cleared CodeQL but tripped the repository's own xss-sink-check. That checker only treats an identifier as safe when it is declared directly from a sanitizer call, so reassigning inside an availability guard left the innerHTML sink looking unsanitized on the changed line. DOMPurify is loaded globally from the local vendored bundle in base.html, before the page script block, so the guard was not buying anything. Sanitizing inline at the sink is both simpler and visible to the checker. Verified locally with scripts/check_xss_sinks.py and functional_tests/test_xss_guardrails_checker.py, both passing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Every gate in this repository is pinned to branches: [Development], so a pull request into the feature/admin-settings-ia integration branch would run only enforce-branch-flow. That would leave the Admin Settings information architecture work with no CodeQL, XSS sink, access-control, malicious-PR, or syntax checking for its whole staging period, which is the opposite of what an integration branch is for. These gates have already earned their keep on this work: CodeQL caught the js/xss-through-dom sink in the Home Page Text preview, and xss-sink-check caught the first attempted fix for it. Adds the staging branch to the pull_request filters only. The push trigger on codeql.yml is deliberately unchanged. release-notes-check is excluded because it already fails on fork pull requests for token reasons and is non-blocking. This is reverted in the final integration -> Development pull request, so Development's own CI configuration is untouched. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…edesign Admin Settings: move Latest Features to the bottom and split the settings template
Cross-references between Admin Settings tabs named a tab button directly, for
example switchTab(event, 'workspaces-tab'). That couples every link to a tab
id, so a rename or regroup breaks it silently: the button no longer exists, no
pane is activated, and the only symptom is a console warning and a URL hash
pointing at nothing.
Two of the twelve links were already broken this way. Citations referenced
Enable Video File Support and Enable Audio File Support with #workspaces, and
the prose said to see the Workspaces tab, but both settings live under Search
and Extract. Those links sent admins to the wrong tab entirely.
Links now declare the card they want with data-admin-link, and the owning tab
is resolved from the DOM at click time via closest('.tab-pane'). Cards can move
between tabs, and tabs can be renamed or regrouped, without touching a link.
The destination card is briefly highlighted so the jump is obvious.
This matters now because the information architecture rework renames and
regroups most tabs, which would have broken all twelve links at once.
switchTab had no callers left and is removed, so the coupling cannot be
reintroduced by copy-paste. The new test fails if a link points at a card that
does not exist, if the href fallback stops matching, or if the old pattern
returns.
Also sanitizes the User Agreement preview inline at the sink. It used the same
guarded-reassignment shape already corrected for the Home Page Text preview,
which reads as unsanitized to static analysis; admin_settings.html now passes a
full-file XSS scan rather than only a changed-lines scan.
Field names and card ids unchanged at 462 and 110, and the 75 functional test
files covering admin_settings.html show the same 33 pre-existing failures.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Several settings only take effect when a different setting is enabled, and the two often live in different tabs. That was communicated four different ways, none of them actionable: a static server-rendered alert, a sentence of prose, a tooltip, or a flash message only after saving. An admin could switch something on and have nothing happen with no visible reason, and no indication of where the prerequisite lived. A dependent card now declares what it needs with data-requires, and a shared module renders an inline notice with two ways to act: a mirror of the prerequisite switch, so it can be satisfied without leaving the tab, and a link to its card using the stage A resolver. The mirror deliberately carries no name attribute. Admin Settings posts one form read by field name, so a second named control would submit the value twice; the mirror drives the canonical input instead and exactly one named control per setting is ever submitted. File Sync warns rather than blocks, because the backend already accepts it as requested and reconciles once Redis is ready. Removing that would take away a deferred-intent path the backend supports on purpose. The Permissions dependency is scoped to the FeedbackAdmin toggle so the unrelated SafetyViolationAdmin toggle in the same card stays usable. Three candidate dependencies are deliberately not wired: backup source blobs are already gated in admin_data_management.js, multi-modal vision falls back to the legacy GPT deployment rather than requiring multi-endpoint, and the Redis Key Vault hint is conditional on a select value and already links correctly. The File Sync server alert is narrowed to the case the client cannot detect, Redis enabled but missing URL or credentials, so the two messages no longer overlap. Client-side gating is advisory. The backend still validates and flashes, and a test asserts that enforcement is still in place. Field names and card ids unchanged at 462 and 110, and the 75 functional test files covering admin_settings.html show the same 33 pre-existing failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Admin Settings IA stage A+B: card-targeted links and dependency announcements
Admin Settings submits a single form and the backend reads every value by field name, so the set of name attributes is the real contract between the template and route_frontend_admin_settings.py. Renaming or dropping one silently stops that setting from saving: no error is raised, the control still renders, and the value simply never arrives. Every stage of the information architecture rework has been verified against a field-name fingerprint, but that check lived in a scratch script and only ran when someone remembered to run it. Stage D moves 96 cards between tabs, which is exactly when an accidental rename is most likely and least visible. The check is now a functional test with the baseline committed alongside it, so it runs in CI on every commit and protects anyone editing these templates later, not just this migration. Adding settings is unaffected: new field names are reported, not failed. Removing or renaming one requires regenerating the baseline in the same commit via --update-baseline, which turns a silent loss into a reviewed decision. The test also rejects duplicate field names outside the five known radio and checkbox groups. That is the property that stops a mirrored control from submitting its value twice, which the Access and Roles roster in stage E depends on. Both assertions were verified to fail correctly: renaming a field is reported by name, and introducing a duplicate is reported with its count. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The rework spans several pull requests into a staging branch, and the context needed to continue it safely has been living in conversation history and in scratch scripts rather than in the repository. Records what has shipped, how to run each guardrail, the regression baseline of 33 pre-existing failures, the three structural complications found while splitting the template, the locked decisions, and the CI branch filters that must be reverted before the staging branch merges into Development. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Admin Settings: enforce the form field contract before stage D moves cards
Admin Settings presented 18 tabs in a single flat list, and that list was written out twice: once as the top tab strip and once as the sidebar. The two copies had already drifted. Tab order differed between them, and Agents, Custom Pages and Search and Extract each carried a different label depending on which navigation you were looking at. Both now render from admin_settings_nav.ADMIN_NAV, which describes the navigation as groups containing tabs containing sections. A change is made once and appears in both. The map was generated from the existing sidebar markup rather than retyped, so it is faithful by construction, and the rendered destinations were compared before and after: the same 18 tabs and the same 86 sections, with per-tab section order preserved. That comparison also surfaced a gap. The Shared Conversation File Approvals card, added upstream while this work was in progress, had no navigation entry at all and could only be found by scrolling. It is now listed like every other setting, which fixes a pre-existing parity failure. The group level is added on top: collapsible headers with persisted state in the sidebar, and a row of group pills that filters the tab strip in the tab layout. Opening a tab reveals its group first in either layout, so a deep link or a cross-reference can never activate a pane whose tab is out of view. Sidebar search now spans groups as well as tabs and sections. No cards move in this change. Field names and card ids are unchanged at 462 and 110, so saving is untouched, and the 75 functional test files covering admin_settings.html go from 33 pre-existing failures to 32. Tests that asserted against hand-written markup now assert against the map, which is the contract both renderings depend on, and a new test rejects a malformed map, a destination with nothing behind it, or a tab hardcoded back into the strip. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Records the navigation map, the new regression baseline of 32 pre-existing failures after stage C fixed a missing navigation entry, and how stage D becomes a two-part edit now that navigation structure lives in one place. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Admin Settings IA stage C: group navigation rendered from one definition
First card move of the information architecture rework, and the pattern the remaining groups follow: split the pane markup, update the navigation map, and add a legacy redirect so existing links keep working. Governance held five cards covering three distinct concerns. It is now three tabs: which features are governed, the policies themselves, and MCP-specific governance. The split is done by a tool that works line by line and refuses to write unless every source line is carried into exactly one output. That immediately caught a real problem: governance-status sat after the last card, so copying the pane trailer into each new pane would have produced three elements sharing one id. It is now rendered once outside the panes, which is also better behaviour, since a status message is no longer trapped on whichever governance tab happened to be active when it was raised. Adds LEGACY_TAB_REDIRECTS so #governance still resolves, now to Feature Governance. Later moves extend the same map. Field names and card ids are unchanged at 462 and 110, and the regression set holds at 32 pre-existing failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…rity Scale mixed three concerns: cache configuration, Cosmos capacity, and Front Door. It is now Redis & Caching and Cosmos, with Front Door moved out entirely. Front Door configures authentication and redirect flows, not throughput, so it sits under Security as a Network tab. That move needed no markup surgery: panes are independent files and groups are entries in the navigation map, so moving a whole tab between groups is a map change alone. Only moving a card between tabs requires touching markup. This pane exercised the interleaved-modal case. Five modals sit between cards rather than collected at the end, and each has to travel with the tab owning its trigger. The split tool assigns the gap after a card to that card, which placed the Redis explorer with Redis, the three maintenance confirmations with Cosmos Maintenance, and the two throughput dialogs with Cosmos Throughput. Verified by listing the modals in each new pane. Extends the legacy redirect map so #scale still resolves. Field names and card ids are unchanged at 462 and 110, and the regression set holds at 32 pre-existing failures. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Records the split pattern that Governance and Scale established, the property that moving a whole tab between groups needs only a navigation map change, and the gap that has to close before the remaining groups can move: the split tool handles one source pane at a time, but the rest pull cards from several. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Admin Settings IA stage D part 1: split Governance and Scale, move Front Door to Security
Move cards across panes so they sit with the settings they actually
work with, without touching a single field name.
Data Lifecycle (new group)
Retention retention-policy-section from workspaces
Classification document-classification-section from workspaces
Archiving conversation-archiving-section from safety
These three decide how long content lives and how it is labelled.
Archiving was under Safety, which describes what it guards against
rather than what it does.
Chat (new group)
Chat Experience processing-thoughts-section from ai-models
chat-file-uploads-section from workspaces
(carries nested conversation-contents-drawer-section)
workspace-scope-lock-section from workspaces
Feedback & Alerts user-feedback-section from safety
desktop-notifications-section from safety
Moves use move_card.py, which refuses to write unless every source line
lands in exactly one output. Nested cards travel with their parent and
the gap after a card travels with it, so interleaved modals stay with
the tab owning their trigger.
Navigation is now 13 groups / 27 tabs / 88 sections.
Verified
field contract 462 names / 110 card ids, unchanged
regression set 32 failures, identical to baseline
jinja compile 29/29 admin templates
xss sinks 5 changed files pass
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Stage D part 2: Data Lifecycle and Chat groups
General had grown into a catch-all of eleven unrelated cards. Security
held one card while a separate Safety tab mixed content filtering with
role permissions. Both are broken up here.
Appearance
Branding branding, home page text, appearance
Notices & Agreements classification banner, chat AI notice, terms of
use, user agreement (from workspaces)
Pages & Links static pages, external links (from general)
Security
Access & Roles permissions-section who gets in, and as what
Secrets keyvault-section was the "security" tab
Content Safety content-safety-section what may be said once in
Session system-settings-section interim home, see below
Network front-door-section
Operations Logging becomes Logging & Health, gaining health check and
API documentation from General.
Help Gains Support Menu from General.
system-settings-section is a mixed card: four of its eight fields are
idle timeout, so Session is the plurality home. The remaining fields
are split out to their real homes in a later change.
Two hardcoded-id traps closed
openKeyVaultSettings switched tabs by the literal id "security-tab",
so the Data Management link to Key Vault broke the moment that tab
was renamed. The function was dead weight: the link now carries
data-admin-link and the tab is resolved from the DOM.
The landing pane was hardcoded twice, as "show active" in one pane
and showAdminTab('general') in the sidebar. Splitting that pane left
Admin Settings with no active pane at all. Every pane now renders
{% if admin_landing_tab == '<id>' %} show active{% endif %}, fed by
get_landing_tab_id(), and the sidebar reads the first rendered tab.
Four test files hardcoded 'general' or 'security'. They now assert the
durable properties instead: Latest Features is last, exactly one pane
resolves active, every tab renders exactly one pane.
Navigation is now 13 groups / 31 tabs / 88 sections.
Verified
field contract 462 names / 110 card ids, unchanged
regression set 32 failures, identical to baseline
jinja compile 33/33 admin templates
xss sinks 41 changed files pass
render check exactly one pane resolves active, and it is the
first tab of the first group
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Search & Extract held eight cards spanning four unrelated jobs, from
Bing consent to voice transcription. Workspaces mixed workspace types
with file rules, workflow and extraction settings.
Knowledge
Web & Research web search, URL access, deep research
Search Index Azure AI Search
Document Extraction document intelligence, chunk sizes, metadata
extraction and multi-modal vision (from
workspaces)
Audio & Video video intelligence, voice conversations
File Sync unchanged
Voice and video sit under Knowledge rather than Chat because they
are extraction pipelines, not conversation features.
Workspaces
Workspace Types personal, group, public
Files & Sharing downloads, sharing, shared conversation file
approvals (from ai-models)
Global Identities unchanged
Workflow is now its own group. It drives approvals and assignment
across every workspace type and was too large to sit as one card.
Navigation is now 14 groups / 36 tabs / 88 sections.
Bug found and fixed by doing this
The Group Workflow Assignment modal sat between two cards, so it
followed file-download-settings-section into Files & Sharing while
its trigger button went to Workflow. An inactive tab pane is hidden,
so the dialog would never have opened.
test_admin_settings_modal_placement.py now checks every modal lives
in a pane that can open it, and that modal ids are unique. Verified
against a deliberately stranded modal: the test fails with exactly
that diagnosis.
Also corrected two section comments that had drifted onto the wrong
cards in ai-models, labelling embeddings as processing thoughts.
AI Models and Agents are deliberately not in this change:
gpt-configuration is not a top-level card, it lives inside
legacyModelSettingsModal, so splitting that pane means moving a modal
rather than a card and deserves its own review.
Verified
field contract 462 names / 110 card ids, unchanged
regression set 32 failures, identical to baseline
jinja compile 38/38 admin templates
xss sinks pass
modal placement 48 modals, all reachable, all ids unique
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Stage D part 3: dismantle General and Security
Stage D part 4: Knowledge, Workspaces and Workflow
The last two catch-all tabs. Both needed modal surgery rather than
plain card moves, which is why they were held back.
AI Models
Model Endpoints multi-endpoint-configuration, and the legacy
model settings modal that holds the Chat Model
card and is opened from it
Embeddings embeddings-configuration
Image Generation image-generation-configuration
Agents & Actions
Agents agents-configuration, agent-template-approvals-section
Actions document-action-capabilities-card, actions-configuration
Inbound MCP inbound-mcp-configuration and its three dialogs
Navigation is now 14 groups / 40 tabs / 88 sections.
Three splitting problems, and how each was solved
gpt-configuration is not a top-level card. It lives inside
legacyModelSettingsModal. The modal was relocated to sit directly
after multi-endpoint-configuration, the card holding its trigger,
and both were then assigned to the same tab so the modal's opening
lines, the nested card and the closing lines landed together and it
reassembled intact.
legacyModelDiscoveryIdentityGuideModal is opened from endpoints,
embeddings and image generation, which now sit in three different
tabs, so it could only ever have opened from one of them. It moved
to the shell, outside every pane. Checked first that it carries no
name= attribute, since moving a field outside the form would change
the save payload. It carries none.
The inbound MCP block is a {% if mcp_ui_enabled %} conditional
spanning a card and three modals. It was lifted out whole into its
own pane rather than split.
Tab-level conditions
The nav map now supports condition on a tab, not just a section, so
a tab whose entire pane is behind a feature flag disappears instead
of rendering empty. Both renderers honour it.
Verified: 40 tabs with mcp_ui_enabled, 39 without.
Also removed seven dangling section comments left at the tail of panes,
each labelling a card that had moved to another tab.
Two location tests asserted document order that only meant something
while everything shared one tab. They now assert the real invariant:
the embeddings and image cards are not inside the legacy modal, and
the document action card leads the tab it actually lives in.
Verified
field contract 462 names / 110 card ids, unchanged
regression set 32 failures, identical to baseline
jinja compile 42/42 admin templates
xss sinks pass
modal placement all modals reachable, all ids unique
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Stage D part 5: AI Models and Agents & Actions
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>
Stage D part 6: Backup & Recovery, completing Stage D
Stage E: split the mixed System Settings card, add the app role roster
Six workflows were gated on branches: [Development] only, so pull requests into the Admin Settings staging branch ran nothing but enforce-branch-flow. The staging branch was added to each filter so every stage was properly checked. That work is finished, so the filters go back to what they were. The workflow files are now byte identical to Development. The final staging to Development pull request runs all gates because it targets Development. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Revert the temporary CI branch filters
Development moved on while the Admin Settings rework was staged, so
the final pull request conflicted. Two files needed resolving.
config.py Both branches independently reached 0.260.011.
Bumped to 0.260.018 so the merged result is clearly
ahead of both.
release_notes Both branches wrote a v0.260.011 section from
parallel work. Their entries are folded into the one
section rather than leaving two headings for the
same version.
Re-verified after the merge
field names 452 on Development, 452 here, none lost, none added
regression set 32 failures, identical to baseline
jinja compile 46/46 admin templates
contract suite 43 tests pass
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Two places still navigated by naming a tab id, and the information architecture rework broke both. The setup walkthrough Each of twelve steps mapped to a tab id. Eleven named tabs that no longer exist, so those steps would have moved nowhere and left the admin looking at whatever was already on screen. Each step already knew which card it was about, so the tab id was a second copy of the same knowledge, free to go stale. Steps now name the card and openAdminCard finds the owning tab from the page. scrollToRelevantSection became redundant and is deleted, along with the old map. Steps 2 and 3 previously pointed at gpt-configuration, which lives inside the legacy model settings dialog and so cannot be scrolled to while that dialog is closed. They now point at multi-endpoint-configuration, the visible card in the same tab and the modern place to configure models. Cosmos throughput validation Clicked scale-tab to reveal an invalid field. scale is a group now, not a tab, so the field needing attention could be left on a hidden tab with no indication of where to look. It now walks up from the invalid field to its own card, so it goes wherever that field actually lives. test_admin_settings_walkthrough_targets.py asserts every step points at an element that exists, that no tab id is hardcoded, and that step numbering has no gaps. A scan confirms no stale '-tab' literal remains in executable admin JavaScript. The rule this rework converged on: never name a tab, name the setting and resolve the tab from the page. Verified regression set 32 failures, identical to baseline admin suite 46 tests pass jinja compile 46/46 admin templates Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Merge Development into staging, and stop navigating by tab id
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.
The Admin Settings information architecture rework, staged and verified across eight pull requests on
feature/admin-settings-ia.Why
Admin Settings had grown to 17 flat tabs in a single 14,344-line template. Related settings were scattered — voice and video sat under a chat heading, conversation archiving under Safety, health checks under General — and Latest Features opened on every visit, which was the original complaint.
What changed
Latest Features is now last and never the landing tab.
The 17 flat tabs became 14 groups / 44 tabs / 93 sections:
Grouping follows what each area actually does and how large it is, not what its title says. Voice and video are extraction pipelines, so they sit under Knowledge. Workflow and Governance were too big to nest inside anything else.
The template is now a shell plus 44 pane partials, and both navigations render from one source of truth (
admin_settings_nav.py).The guarantee: no setting changed
Admin Settings posts a single form and the backend reads by field
name. That is the only thing that can break, so it was measured on every stage — and again after mergingDevelopmentin:Not one field was renamed, dropped or duplicated. Every saved value survives; nothing needs re-entering.
Bugs found and fixed along the way
These were all latent — most only surfaced because something moved:
Guardrails added
Eleven new test files, including:
test_admin_settings_field_contract.py— the field-name baseline. The one test that must never be updated casually.test_admin_settings_modal_placement.py— every dialog must sit where it can be opened. Verified against a deliberately stranded modal.test_admin_settings_template_composition.py— pane balance, and a check that no test reads the template uncomposed.test_admin_settings_group_shared_regions.py— asserts all 1,608 element ids are unique.test_admin_access_roles_roster.py— mirrors must carry noname.Several existing tests hardcoded specific tab ids. They now assert durable properties — Latest Features is last, exactly one pane is active, every tab renders one pane — rather than names that legitimately change.
New capabilities
require_member_of_*settings spread across seven tabs are now listed together in Security → Access & Roles, built from the page so it cannot go stale.Verification
DevelopmentHousekeeping
.github/workflowsis byte-identical toDevelopment.Developmentmoved on during this work, so it was merged in via Merge Development into staging, and stop navigating by tab id #1320. Both branches had independently reached0.260.011; their release notes are combined under that version and the result bumped to 0.260.018.Merged stages
#1297, #1304, #1306, #1307, #1309, #1310, #1311, #1313, #1314, #1315, #1316, #1318, #1320
Version: 0.260.018