Skip to content

Admin app: /Admin is the Admin app, personal settings are the person's own app, Inbox app for every user - #5807

Merged
rbuergi merged 5 commits into
mainfrom
feat/admin-app
Sep 27, 2026
Merged

rbuergi merged 5 commits into
mainfrom
feat/admin-app

Conversation

@rbuergi

@rbuergi rbuergi commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Why

Maintainer, on a freshly installed control instance: "https://control.systemorph.com/Admin shows an empty screen. should this be the admin app? … don't make it of type Space … link it to some Admin app which is installed by default and shows me all the about, who I am, all the settings", then "all which should be seen only by global admin ⇒ move to this app. remove from settings", "do same for user settings" and "every new user should get Inbox app installed".

What /Admin rendered before, measured from code: no code registered the Admin partition root. On a fresh database (the new control instance) there is no row, so PathResolutionService.SynthesizePartitionRoot resolved a TYPELESS placeholder whose hub activated on the default configuration — an empty page. On long-lived databases the row exists as a Space (read on memex.systemorph.com and memex.meshweaver.cloud: nodeType: Space, no content), mirrored into the auth Spaces catalog — so "Admin" was listed among a platform admin's workspaces and rendered as an empty Space.

What it renders now

/Admin is the Admin app — AdminAppNodeType registers the AdminApp NodeType and the Admin root in code (so it is in every image's closed type set, the control image included); the app is the Settings page of the Admin node, tabs at /Admin/Settings/{id}. /{user}/Settings is the person's own settings app, for its owner only. Full inventory: Doc/Architecture/AdminApp (in this PR).

Surface Before After Gate
Overview (host, version/commit, runtime, registry + instance id, closed type set; then About) — /Admin platform admin
Who am I MCP/REST whoami only /Admin/Settings/WhoAmI, /{user}/Settings/Account admin / owner
Global Administration (Admin/_Access, + Add Admin) /{user}/Settings/GlobalAdmin /Admin/Settings/GlobalAdmin platform admin
Data Sources /_Setting/GlobalSettings/DataSources (first tab for EVERY signed-in user) /Admin/Settings/DataSources platform admin
Access Control / Effective Access of Admin unreachable /Admin/Settings/AccessControl permission on Admin
Coupons, Instance grants, Composition /{user}/Settings/{id} /Admin/Settings/{id} platform admin
Invitations, Inbox (mail), Updates (Admin/UpdatePolicy), Published, Privacy /_Setting/GlobalSettings/{id} /Admin/Settings/{id} AdminOnly + NodeTypes:[AdminApp]
Registration, Instances, Partitions, Sign-in providers, Token Usage, AI Admin see the MeshWeaver.Plugins PR /Admin/Settings/{id} platform admin
Preferences, Notifications, API Tokens, MeshWeaver Instances, Account /{user}/Settings (any viewer could open it: User nodes are public-read) /{user}/Settings, owner only owner
About, What's New /_Setting unchanged every signed-in user
Inbox app — /{user}/Inbox, tile {user}/_App/Inbox seeded by run-once seed-inbox-app for every user (first admin included) owner

A non-admin opening /Admin is refused by the Admin partition itself (unchanged — nothing widens a read on Admin/_Access), and every admin tab additionally waits for a positive IsGlobalAdmin. Old admin links redirect into the app (RelocateSettingsTabsToAdminApp, honoured by the per-node and the global settings page).

Tests

  • AdminAppTest (new): the Admin root is AdminApp (fails on main: no node exists); the workspace listing of a platform admin does not list Admin (control: a real Space is listed); the platform admin finds Global Administration / Data Sources / Who am I / Access Control and no node-management tabs; /Admin with no area lands on the app; a non-admin reads NOTHING of a persisted Admin/_Access grant (control: the admin does); a non-admin opening /Admin sees no administration; own settings hold only personal tabs for an ordinary user AND a platform admin; someone else's settings are refused; /{admin}/Settings/GlobalAdmin redirects to /Admin/Settings/GlobalAdmin.
  • InboxAppTest (new): a freshly created first admin and an invited user both get {user}/_App/Inbox through the real LogonActionRunner, run twice; the action is idempotent, run-once, stable key; the page anchors every leg on the owner, band order, each query once.
  • PlatformSettingsTabSeedTest updated to the Admin-app lane; AnInstanceReportsWhatItRunsTest caught a declaration without a NodeTypeDefinition (fixed in this PR).
  • Local, CI flags: dotnet build MeshWeaver.slnx -c Release -warnaserror 0 errors / 0 warnings; MeshWeaver.Graph.Test 2166/2166, Memex.Portal.Shared.Test 2054 (the 2 reds were the missing definition, now green), MeshWeaver.Documentation.Test 649/649, LocalizationTest 66/66.
  • Found while testing, pre-existing and NOT changed here: in the Monolith harness, nodes seeded through AddMeshNodes (static provider) are not row-filtered — an ordinary user lists another user's static _Access grant — so the access test asserts on a runtime-written grant, as production holds.

How it reaches an instance

Compiled platform code → an image roll (hop C). The companion MeshWeaver.Plugins PR carries migration V59 (DbVersion.Latest 59) that retypes the persisted Admin Space row to AdminApp and drops its auth mirror row; the roll runs it before the new pods serve. The control image is the same Memex.Portal.Distributed host with Mesh:ClosedTypeSet=true; AdminApp is registered in code by AddGraph, so it is in the closed set — no package install. After the roll, recycle Admin (a live activation bound to the old Space/typeless configuration serves it until a DisposeRequest reaches it).

Pairs-with: none — no public type or member leaves src/ (UserNodeType's global-admin tab methods were private; GlobalSettingsLayoutArea.BuildDataSourcesTab is internal). The MeshWeaver.Plugins PR consumes the new AdminAppNodeType API and lands AFTER this one.
Implementers: none — no interface gains a member.
Mirror-sync: npm run sync:i18n -- --ref <merged core sha> in MeshWeaver.Plugins clients/react, handed to the companion Plugins PR (41 new keys: adminApp.*, whoAmI.*, inbox.*, globalAdmin.*, settings.whoAmI/account/ownSettingsOnly/dataSourcesIntro/mySettings; no existing value changed).

🤖 Generated with Claude Code

rbuergi and others added 2 commits September 27, 2026 14:24
…n's own app; Inbox app for every user

/Admin rendered an empty page: no code registered the Admin partition root, so a fresh
database resolved it to a synthesized typeless placeholder (default hub configuration),
and long-lived databases carried a persisted Space row that also listed "Admin" among a
platform admin's workspaces.

- AdminAppNodeType: the AdminApp NodeType and the Admin root registered in code (part of
  every image's closed type set, the control image included). /Admin opens the Settings
  page of the Admin node; tabs at /Admin/Settings/{id}. Default node-management tabs are
  hidden (HideSettingsTabs); Access Control / Effective Access of Admin stay.
- AddAdminAppTab / AdminOnlyTab: a tab yields only on the Admin hub and only after a
  POSITIVE IsGlobalAdmin; relocated ids redirect from any other settings page
  (RelocateSettingsTabsToAdminApp, honoured by SettingsLayoutArea and GlobalSettings).
- Moved into the app: Global Administration (was the admin's own user settings), Data
  Sources (was the first global-settings tab every user landed on), Coupons, Instance
  grants, Composition; and the Invitations / Inbox / Updates / Published / Privacy seeds
  (NodeSettings + AdminOnly + NodeTypes:[AdminApp]). New: Overview (instance facts + About)
  and Who am I.
- /{user}/Settings opens for its owner only (User nodes are public-read) and gains an
  Account (who am I) tab.
- Inbox app: /{user}/Inbox renders the inbox legs for the owner; SeedInboxAppLogonAction
  (run-once, create-if-absent) installs the tile for every user incl. the first admin.
- Doc: Doc/Architecture/AdminApp (inventory before -> after -> gate); references updated.

Tests: AdminAppTest (Admin is the app, not a Space; admin finds the surfaces; /Admin default
page; a non-admin reads nothing in Admin/_Access; own settings hold only personal tabs; other
people's settings refused; old admin link redirects), InboxAppTest (first admin + invited
user get the Inbox app through the real runner, idempotent), seed test updated.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The deployment report's adopted-framework inventory reads every NodeType node as a
NodeTypeDefinition and refused the AdminApp declaration, which carried none
(AnInstanceReportsWhatItRunsTest caught it).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 27, 2026 12:38
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 3)

461 tests   461 ✅  56s ⏱️
  3 suites    0 💤
  3 files      0 ❌

Results for commit b34d29c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 0)

  1 files    1 suites   3m 14s ⏱️
348 tests 348 ✅ 0 💤 0 ❌
352 runs  352 ✅ 0 💤 0 ❌

Results for commit b34d29c.

♻️ This comment has been updated with latest results.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Authorization gates can remain stale after revocation, and Inbox rendering and seeding have unresolved issues.

Review effort: Lite
Findings: 1 High severity · 3 Medium severity · 2 Low severity

Open (6)
What changed in this PR

This PR introduces a dedicated /Admin app, owner-only personal settings, and a per-user Inbox app.

Changes:

  • Adds Admin app registration, gated administration tabs, redirects, and localization.
  • Restricts personal settings to owners.
  • Seeds and renders Inbox apps for users.
  • Updates tests, documentation, and portal configuration.
File Reviewed change
test/​NodePagesWithoutProvenance.allow Updates provenance allowances.
test/​MeshWeaver.Graph.Test/​InboxAppTest.cs Tests Inbox seeding and layout.
test/​MeshWeaver.Graph.Test/​AdminAppTest.cs Tests Admin access and routing.
test/​Memex.Portal.Shared.Test/​PlatformSettingsTabSeedTest.cs Validates relocated settings tabs.
src/​MeshWeaver.PluginCatalog/​InstanceGrantAdminSettingsTab.cs Relocates instance grants.
src/​MeshWeaver.PluginCatalog/​CouponAdminSettingsTab.cs Relocates Coupons.
src/​MeshWeaver.PluginCatalog/​CompositionAdminSettingsTab.cs Relocates composition settings.
src/​MeshWeaver.Messaging.Hub/​Localization/​strings.en.json Adds English strings.
src/​MeshWeaver.Messaging.Hub/​Localization/​strings.de.json Adds German strings.
src/​MeshWeaver.Graph/​UserActivityLayoutAreas.cs Adds Inbox app metadata.
src/​MeshWeaver.Graph/​SettingsLayoutArea.cs Redirects relocated settings links.
src/​MeshWeaver.Graph/​Logon/​SeedInboxAppLogonAction.cs Seeds Inbox records at logon.
src/​MeshWeaver.Graph/​InboxLayoutArea.cs Renders Inbox sections.
src/​MeshWeaver.Graph/​GlobalSettingsLayoutArea.cs Moves Data Sources to Admin.
src/​MeshWeaver.Graph/​Configuration/​WhoAmISettingsTab.cs Adds identity and permission views.
src/​MeshWeaver.Graph/​Configuration/​UserNodeType.cs Adds owner-only settings and Inbox routing.
src/​MeshWeaver.Graph/​Configuration/​SettingsMenuItemsExtensions.cs Adds hidden settings-tab filtering.
src/​MeshWeaver.Graph/​Configuration/​LogonActionNodeType.cs Registers Inbox seeding.
src/​MeshWeaver.Graph/​Configuration/​GraphConfigurationExtensions.cs Registers the Admin app type.
src/​MeshWeaver.Graph/​Configuration/​GlobalSettingsMenuItemsExtensions.cs Relocates global settings.
src/​MeshWeaver.Graph/​Configuration/​GlobalAdministrationTab.cs Adds global administration.
src/​MeshWeaver.Graph/​Configuration/​AdminAppNodeType.cs Defines the Admin app and access gates.
src/​MeshWeaver.Documentation/​Data/​DataMesh/​Spaces.md Updates navigation documentation.
src/​MeshWeaver.Documentation/​Data/​Architecture/​PluginRegistry.md Updates admin navigation.
src/​MeshWeaver.Documentation/​Data/​Architecture/​InvitationOnlyOnboarding.md Updates invitation navigation.
src/​MeshWeaver.Documentation/​Data/​Architecture/​Instances.md Updates Instances navigation.
src/​MeshWeaver.Documentation/​Data/​Architecture/​GitHubSync.md Updates partition navigation.
src/​MeshWeaver.Documentation/​Data/​Architecture/​EnvironmentComposition.md Updates Composition navigation.
src/​MeshWeaver.Documentation/​Data/​Architecture/​AdminApp.md Documents the application split.
src/​MeshWeaver.Documentation/​Data/​Architecture/​AccessControl.md Updates admin-gate references.
src/​MeshWeaver.Documentation/​Data/​Architecture.md Adds the Admin app documentation link.
memex/​Memex.Portal.Shared/​Settings/​PlatformSettingsTabAreas.cs Moves platform tabs to Admin.
memex/​Memex.Portal.Shared/​Settings/​AdminAppOverviewTab.cs Adds the Admin overview tab.
memex/​Memex.Portal.Shared/​MemexConfiguration.cs Registers Admin portal configuration.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +146 to +152
return host.Hub.IsGlobalAdmin(viewerId)
.Where(isAdmin => isAdmin)
.Take(1)
.Select(_ => (IReadOnlyList<SettingsMenuItemDefinition>)[tab])
.Timeout(AdminCheckBound)
.Catch<IReadOnlyList<SettingsMenuItemDefinition>, Exception>(_ => Observable.Return(none))
.StartWith(none);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9a4b6ad. The gate is now AdminAppNodeType.LiveAdminVerdict — IsGlobalAdmin(viewer).Catch(→false).StartWith(false).DistinctUntilChanged(), no Where/Take(1)/Timeout — and AdminOnlyTab maps every emission: true → [tab], false → []. A grant revoked while the menu is open removes the tab. Pinned by AdminAppTest.TheAdminVerdict_FollowsARevokedGrant (grant written at runtime, verdict observed true, grant deleted, verdict observed false on the same subscription).

Comment on lines +99 to +104
var isAdmin = hub.IsGlobalAdmin(session.UserId)
.Where(a => a).Take(1)
.Timeout(TimeSpan.FromSeconds(5))
.Catch<bool, Exception>(_ => Observable.Return(false))
.StartWith(false)
.DistinctUntilChanged();

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9a4b6ad. The Who-am-I grid now combines the same live LiveAdminVerdict with the two permission folds, so the rows are recomputed on every emission and Yes turns back to No on revocation. Pinned by TheAdminVerdict_FollowsARevokedGrant (the stream) and TheWhoAmIRows_ShowTheCurrentVerdict (rows follow the verdict both ways).

Comment on lines +67 to +72
var seen = new HashSet<string>(StringComparer.Ordinal);
return InboxQueries.Resolve(legs, owner)
.OrderBy(leg => BandOrder.IndexOf(leg.Band) is var i and >= 0 ? i : BandOrder.Length)
.ThenBy(leg => leg.Band, StringComparer.Ordinal)
.ThenBy(leg => leg.Order)
.Where(leg => seen.Add(leg.Query))

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9a4b6ad, at the queries rather than the dedup key. Running is the ActivityStatus enum default and is OMITTED from stored content, so neither content.status:Running nor its negation can separate the bands (the trap OutboundEmailWatchQueryTest documents). Running now negates each stamped terminal state (-content.status:Succeeded -…Warning -…Failed -…Cancelled); Recent matches any of them (content.status:Succeeded|Warning|Failed|Cancelled). New tests: InboxAppTest.ThePage_ShowsThreeDistinctBands (NeedsYou, Running, Recent all render) and RunningAndRecent_SplitTheOwnersActivities (a running activity lands only in Running, a finished one only in Recent, on a real partition). InboxLegsAreAnchoredTest.NoLeg_UsesANamespaceAlternation now inspects the namespace:/path: anchor terms only — the content-field alternation filters inside the one partition.

return Observable.Return(LogonActionOutcome.Nothing);

var node = UserActivityLayoutAreas.BuildAppRecord(ownerId, UserActivityLayoutAreas.InboxAppSpec(ownerId));
return mesh.CreateNode(node)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9a4b6ad: the create is now access.RunAs(context.Identity, () => mesh.CreateNode(node)) at the write site, exactly as LogonActionRunner.Commit does. New InboxSeedWritesAsTheOwnerTest (no public-admin grants) sets a foreign ambient identity with no rights on the owner's partition and subscribes the action on a pool thread: without the RunAs it fails with Access denied: Create permission required for node 'inbox-runas-owner/_App/Inbox' (verified by reverting locally), with it the tile lands.

["Store"] = ("Store", "/static/NodeTypeIcons/shopping-bag.svg"),
["Doc"] = ("Documentation", "/static/NodeTypeIcons/book.svg"),
["~/" + ChatArea] = ("Threads", ThreadsIcon),
["~/" + InboxLayoutArea.AreaName] = ("Inbox", InboxIcon),

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9a4b6ad + Plugins#2450 (c358d416a). App gains LabelKey; the Inbox record carries inbox.title, and the launcher (MeshSearchView.TileLabel, Plugins) resolves it in the VIEWER's language, falling back to the stored name. The stored Name is seeded in the owner's language (context.Identity.Locale) for any surface that paints the name alone. Tests: InboxAppTest.TheTile_IsLocalized (German owner → Posteingang, key stored) and Plugins AppTileLabelTest (German viewer sees Posteingang from typed and raw-JSON content; no/unknown key keeps the stored name).

Comment on lines +107 to +109
var nodes = await QueryAs(PlatformAdmin, $"path:{AdminAppNodeType.Path}", TestContext.Current.CancellationToken);

var admin = nodes.Should().ContainSingle(n => n.Path == AdminAppNodeType.Path).Subject;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 9a4b6ad: TheAdminRoot_IsTheAdminApp reads Mesh.GetWorkspace().GetMeshNodeStream(AdminAppNodeType.Path) as the platform admin — the authoritative read of the one known path — instead of the query index.

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 1)

1 693 tests   1 693 ✅  4m 47s ⏱️
    2 suites      0 💤
    2 files        0 ❌

Results for commit b34d29c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 4)

    3 files      3 suites   6m 50s ⏱️
2 232 tests 2 232 ✅ 0 💤 0 ❌
2 233 runs  2 233 ✅ 0 💤 0 ❌

Results for commit b34d29c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 2)

764 tests   573 ✅  7m 8s ⏱️
  3 suites  191 💤
  3 files      0 ❌

Results for commit b34d29c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results (shard 5)

    5 files      5 suites   14m 49s ⏱️
4 281 tests 4 279 ✅ 2 💤 0 ❌
4 285 runs  4 283 ✅ 2 💤 0 ❌

Results for commit b34d29c.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Test Results

   17 files     17 suites   37m 46s ⏱️
9 779 tests 9 586 ✅ 193 💤 0 ❌
9 788 runs  9 595 ✅ 193 💤 0 ❌

Results for commit b34d29c.

♻️ This comment has been updated with latest results.

…at the seed's write, localized Inbox tile

- AdminOnlyTab and the Who-am-I grid observe the admin verdict LIVE
  (AdminAppNodeType.LiveAdminVerdict): a revoked grant removes the tab and turns Yes to No;
  no .Where(true).Take(1) one-shot on a stream feeding a live view.
- InboxQueries: Running = no stamped terminal status, Recent = any stamped terminal status
  (Running is the omitted enum default, so it cannot be matched either way); all three bands
  render. The anchor guard now checks namespace:/path: terms only.
- SeedInboxAppLogonAction: the create runs under access.RunAs(context.Identity, ...) at the
  write site, as LogonActionRunner.Commit does. InboxSeedWritesAsTheOwnerTest subscribes it
  on a pool thread under a foreign ambient identity: refused without the fix, green with it.
- App.LabelKey: the Inbox record carries inbox.title (resolved per viewer by the launcher,
  Plugins) and its stored name is seeded in the owner's language.
- AdminAppTest reads the Admin node through GetMeshNodeStream, not the query index; new
  TheAdminVerdict_FollowsARevokedGrant and TheWhoAmIRows_ShowTheCurrentVerdict.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@meshweaver-cloud
meshweaver-cloud Bot added this pull request to the merge queue Sep 27, 2026
@rbuergi
rbuergi enabled auto-merge September 27, 2026 16:47
@rbuergi

rbuergi commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Adopting this branch to resolve the merge conflict with main for the maintainer (branch idle since 13:20Z, PR is DIRTY). I will merge origin/main into feat/admin-app, preserve both sides' intent, build the touched projects with -c Release -warnaserror, and push. No change to the PR's scope.

rbuergi and others added 2 commits September 27, 2026 19:22
…'s additions)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…pp tab too

The merge of main unioned the localization keys but left main's new
admin-only "Control lane" seed on the global settings page. The merged seed
test lists ControlLane among the admin tab ids (main) and requires every admin
tab to be an Admin-app tab (this branch), so PlatformSettingsTabSeedTest failed
3 of 6 at bac07bd. Move it like the other Administration seeds:
NodeSettings context, gated AdminOnly + NodeTypes [AdminApp], and listed in
AdminAppTabIds so the old /GlobalSettings/ControlLane link redirects to
/Admin/Settings/ControlLane. AdminApp.md's inventory lists it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@rbuergi

rbuergi commented Sep 27, 2026

Copy link
Copy Markdown
Contributor Author

Merge with main is on the branch (bac07bd, the concurrent merge that landed while I was resolving; mine resolved the same catalog conflict identically: union of both sides' keys, en/de key sets equal, no duplicates). It missed one semantic conflict: main's #5791 added an admin-only Control lane seed to the global settings page, while this PR moves every Administration tab into the Admin app. The merged seed test lists ControlLane as an admin tab, so PlatformSettingsTabSeedTest failed 3 of 6 at bac07bd (measured locally). b34d29c relocates it like the other admin tabs: NodeSettings context, AdminOnly + NodeTypes: [AdminApp], and an AdminAppTabIds entry so /GlobalSettings/ControlLane redirects to /Admin/Settings/ControlLane. It also adds the tab to AdminApp.md's inventory. Release -warnaserror gave 0 errors on each touched project. The targeted tests passed: 66 LocalizationTest, 37 Portal.Shared, 102 Graph. Prose that says "Settings ▸ Control lane" (the hosting.annKey.* values and SelfUpdateAnnouncementKey.md) is unchanged, so Mirror-sync still holds with no existing value changed.

@rbuergi
rbuergi merged commit f78fcf6 into main Sep 27, 2026
43 checks passed
@rbuergi
rbuergi deleted the feat/admin-app branch October 10, 2026 13:10
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.

2 participants