diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 60ac40224..12300fe0e 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -8311,3 +8311,13 @@ Related command targets do not retarget an alert. The funded OpenRouter `openai/gpt-6-astra` route has explicitly reviewed, tiered cost estimates dated 2026-09-07. Unreviewed aliases stay unknown. Its history-explanation qualification is distinct from the earlier Gemini lab matrix and from wider rollout evidence. + +### Compact shell Assistant coexistence + +The mobile Assistant launcher remains in shared header flow. That header and +its controls may wrap at compact widths so an entitled Organization selector +does not displace session actions or the connection indicator beyond the page. +Keep desktop launcher placement and existing visibility decisions unchanged; +wrapping must not introduce entitlement reads or alter Assistant authority. +AppLayout component and architecture regressions preserve these boundaries; +real-backend phone geometry proof is retained by the Organization mobile audit. diff --git a/docs/release-control/v6/internal/subsystems/cloud-paid.md b/docs/release-control/v6/internal/subsystems/cloud-paid.md index 4bedecd99..1c13c6278 100644 --- a/docs/release-control/v6/internal/subsystems/cloud-paid.md +++ b/docs/release-control/v6/internal/subsystems/cloud-paid.md @@ -3462,3 +3462,13 @@ A failed reconnect admission request retains the current tenant's last valid facet. The synchronous organisation-switch reset still clears outgoing admission before requesting the new tenant, including when that request fails. This is navigation continuity, not an authorization or entitlement decision. + +### Compact entitled Organization chrome + +An entitled self-hosted Organization selector must not push session controls +outside a compact viewport. The shared header may wrap on phones while retaining +desktop placement; this must not change organization scope, feature gates or +activation semantics. The offline mobile audit explicitly activates the default +context before it creates and activates isolated organizations, using the normal +authenticated API. Its route/overflow checks and AppLayout regression cover this +boundary without granting Community private RBAC or changing CI tier membership. diff --git a/docs/release-control/v6/internal/subsystems/frontend-primitives.md b/docs/release-control/v6/internal/subsystems/frontend-primitives.md index d3cf8d562..96ac5dcd5 100644 --- a/docs/release-control/v6/internal/subsystems/frontend-primitives.md +++ b/docs/release-control/v6/internal/subsystems/frontend-primitives.md @@ -7280,3 +7280,17 @@ must not clear a background history filter or invalidate its return-focus target Ordinary search shortcuts resume when the background becomes interactive again. When Assistant is already open, a desktop-to-phone transition must retain that destination rather than reopening the underlying history drawer above it. + +### Compact Organization header wrapping + +The shared header and its controls must wrap below the small-screen breakpoint +instead of forcing the document wider when an entitled Organization selector +joins the session controls. Keep the selector, kiosk/logout controls and +connection indicator available; do not hide overflow to conceal an inaccessible +action. Desktop grid placement remains unchanged. + +Proof: `AppLayout.test.tsx` pins wrapping and retained logout semantics. The +signed-offline `05-settings-mobile-audit.spec.ts` exercises real Organization, +Access and Sharing routes at 320px and 390px, with app-shell width and full-scroll +assertions; header screenshots retain the compact layout. This is independent +of private RBAC implementation and hosted probation acceptance. diff --git a/frontend-modern/browser-verification.json b/frontend-modern/browser-verification.json index da66077c5..b0f550971 100644 --- a/frontend-modern/browser-verification.json +++ b/frontend-modern/browser-verification.json @@ -1,39 +1,42 @@ { "version": 1, - "base_sha": "6f3547bc5fbb10ddf313a8cd2f184b6359173a59", - "verified_at": "2026-09-08T22:29:31.643744Z", + "base_sha": "14a109444c6d1cad420797ea30f279e77b6fc517", + "verified_at": "2026-09-09T10:42:06.278902Z", "result": "passed", "changed_paths": [ - "frontend-modern/src/components/Alerts/useWebhookConfigState.ts" + "frontend-modern/src/AppLayout.tsx" ], "content_sha256": { - "frontend-modern/src/components/Alerts/useWebhookConfigState.ts": "14f0dceb069fe52b9a47100c50998ae53a2c5c8a29a524e342f7458546cb4a19" + "frontend-modern/src/AppLayout.tsx": "71cbc527fae0dbae3891174bb8a140d90f50917405d6c15a3b0d8ae7dcb31644" }, "routes": [ - "/qualification (real WebhookConfigForm and hook; synthetic callbacks)" + "/settings/organization", + "/settings/organization/access", + "/settings/organization/sharing" ], "viewports": [ { - "width": 1440, - "height": 900 - }, - { - "width": 900, - "height": 900 + "width": 320, + "height": 568 }, { "width": 390, - "height": 900 + "height": 844 + }, + { + "width": 1280, + "height": 720 } ], "states": [ - "Unsaved manually entered Pushover aliases", - "Test callback payload", - "Save callback payload" + "Signed entitled default and created organizations; non-demo Community backend", + "Access invitation and outgoing sharing fixture", + "Desktop incoming share pending then accepted" ], "interactions": [ - "Click Test then Add Webhook at each width; assert both canonical token/user payloads, no page errors or horizontal overflow." + "Chromium and mobile WebKit: switch organization; audit three routes at both phone widths; reach shell bottom and assert page/shell width within one pixel", + "Desktop Chromium Scenario 6: verify owner identity relationships, editor/viewer roles and pending approval; click Accept and verify accepted state via API" ], - "notes": "Real component with Vite dev transform in Chromium, not production build or installed/provider delivery. Phone screenshot visually inspected; synthetic credentials only. Screenshots /tmp/pulse-webhook-parity.", - "command": "pulse-heavy-run -- node scripts/check-webhook-test-save-parity.mjs" + "command": "pulse-heavy-run -- bash -c \"cd tests/integration && PULSE_E2E_USE_LOCAL_BACKEND=true node scripts/with-offline-entitlements.mjs node scripts/run-playwright.mjs tests/05-settings-mobile-audit.spec.ts --project=chromium --project=mobile-safari --grep settings/organization --workers=1 --retries=0\"; separate diagnostic config Scenario 6 desktop run", + "notes": "Exact actual mobile grep: settings/organization(/(sharing|access))?$ . Six mobile route tests passed (53.0s); desktop sharing scenario passed (3.8s). Source-built real Community backend, ephemeral loopback signing issuer, no mock mode or signature bypass. Phone Chromium/WebKit and desktop acceptance screenshots inspected. Evidence retained in Web20260909T103517Z outcome directory browser-proof, org-fixed.log and scenario6-desktop.log. Not private RBAC, hosted probation clearance or release qualification." } diff --git a/frontend-modern/src/AppLayout.tsx b/frontend-modern/src/AppLayout.tsx index c00ce6a70..33c7fb981 100644 --- a/frontend-modern/src/AppLayout.tsx +++ b/frontend-modern/src/AppLayout.tsx @@ -741,7 +741,7 @@ export function AppLayout(props: AppLayoutProps) { />
{renderAssistantLauncher(AI_CHAT_MOBILE_LAUNCHER_BUTTON_CLASS)} diff --git a/frontend-modern/src/__tests__/App.architecture.test.ts b/frontend-modern/src/__tests__/App.architecture.test.ts index 00ba82307..11c8cf9ab 100644 --- a/frontend-modern/src/__tests__/App.architecture.test.ts +++ b/frontend-modern/src/__tests__/App.architecture.test.ts @@ -571,6 +571,20 @@ describe('App architecture', () => { expect(retiredRouteNavigations).toEqual([]); }); + it('keeps compact organization chrome presentational and its offline audit explicitly entitled', () => { + expect(appLayoutSource).toContain(''); + expect(appLayoutSource).toContain('selectedOrgId={props.activeOrgID()}'); + expect(appLayoutSource).toContain('onChange={props.onSwitchOrg}'); + expect(appLayoutSource).not.toContain('/api/license/activate'); + const audit = readFileSync(join(integrationTestsDir, '05-settings-mobile-audit.spec.ts'), 'utf8'); + const preparation = audit.slice(audit.indexOf('const prepareOrganizationAuditFixture'), audit.indexOf('const test = base.extend')); + const activation = preparation.indexOf('await activateOfflineOrganization(page)'); + expect(activation).toBeGreaterThan(preparation.indexOf('await ensureSessionAuthenticated(page)')); + expect(activation).toBeLessThan(preparation.indexOf('await createOrg(page')); + expect(activation).toBeGreaterThan(0); + expect(preparation).toContain('process.env.PULSE_E2E_OFFLINE_ACTIVATION_KEY'); + }); + it('keeps authenticated chrome in AppLayout and hosted bootstrap in useAppRuntimeState', () => { expect(appLayoutSource).toContain('export function AppLayout(props: AppLayoutProps)'); expect(appLayoutSource).toContain( diff --git a/frontend-modern/src/__tests__/AppLayout.test.tsx b/frontend-modern/src/__tests__/AppLayout.test.tsx index 6d25667ee..0073a4ab3 100644 --- a/frontend-modern/src/__tests__/AppLayout.test.tsx +++ b/frontend-modern/src/__tests__/AppLayout.test.tsx @@ -510,6 +510,14 @@ describe('AppLayout navigation icons', () => { expect(launcher.closest('.header-controls')).toBeInTheDocument(); }); + it('allows mobile header controls to wrap without hiding organization or session actions', () => { + setViewportWidth(320); + const { container } = renderLayout(); + expect(container.querySelector('.header')).toHaveClass('flex-wrap', 'sm:flex-nowrap'); + expect(container.querySelector('.header-controls')).toHaveClass('max-w-full', 'flex-wrap', 'sm:flex-nowrap'); + expect(screen.getByRole('button', { name: 'Logout' })).toBeInTheDocument(); + }); + it('preserves the Assistant edge launcher on desktop', () => { setViewportWidth(1440); renderLayout(); diff --git a/tests/integration/tests/05-settings-mobile-audit.spec.ts b/tests/integration/tests/05-settings-mobile-audit.spec.ts index 1d2fe20e8..28fb41dee 100644 --- a/tests/integration/tests/05-settings-mobile-audit.spec.ts +++ b/tests/integration/tests/05-settings-mobile-audit.spec.ts @@ -5,6 +5,8 @@ import { fileURLToPath } from "node:url"; import { test as base, expect } from "@playwright/test"; import { apiRequest, + activateOfflineOrganization, + ensureSessionAuthenticated, createAuthenticatedStorageState, createOrg, } from "./helpers"; @@ -98,6 +100,11 @@ const prepareOrganizationAuditFixture = async ( if (!(ORGANIZATION_SETTINGS_ROUTES as readonly string[]).includes(route)) return []; + if (process.env.PULSE_E2E_OFFLINE_ACTIVATION_KEY) { + await ensureSessionAuthenticated(page); + await activateOfflineOrganization(page); + } + await page.goto("/settings/infrastructure", { waitUntil: "domcontentloaded", }); @@ -255,6 +262,11 @@ test.describe("Settings mobile optimization audit", () => { audit.pageWidth, `Mobile overflow on ${route} at ${viewport.width}px (viewport=${audit.viewportWidth}, page=${audit.pageWidth}, offenders=${JSON.stringify(audit.offenders)})`, ).toBeLessThanOrEqual(audit.viewportWidth + 1); + if ((ORGANIZATION_SETTINGS_ROUTES as readonly string[]).includes(route)) { + // Retain the header as well as the bottom-of-content width proof. + await page.locator(".app-scroll-shell").evaluate((element) => { element.scrollTop = 0; }); + await page.screenshot({ path: test.info().outputPath(`organization-${viewport.width}.png`) }); + } } } finally { if (createdOrgIDs.length > 0) {