fix(spa): detail-header toolbar overflow (#672) + legacy-iframe refused-fallback (#673) + 1.11.1#676
Merged
Conversation
…olbar (#672) The detail-page header already stacked breadcrumb / title / toolbar as three full-width rows (#658/#674), but a ModelAdmin with 8+ actions still overflowed horizontally and pushed the H1 + breadcrumb off-screen. Root cause: the content column `<main>` is a flex item, whose default `min-width: auto` refuses to shrink below its widest content. A toolbar with 12+ buttons made that intrinsic width exceed the viewport, so `flex-1` blew `main` past the viewport edge and dragged every stacked row (title and breadcrumb included) off-screen — no header re-stacking could help. `min-w-0` on `<main>` lets it shrink to the viewport so the toolbar's `flex-wrap` actually reflows. - Layout: `<main>` gets `min-w-0`. - DetailPage toolbar row: `w-full min-w-0 flex-wrap`; Edit/Delete cluster stays right-aligned (`ml-auto`) on the last line regardless of action count. - ObjectActionButton: long labels wrap inside the button (`whitespace-normal break-words`) instead of forming a wide min-content box. - examples/many_actions PipelineAdmin fixture: 12 batch + 2 detail-only actions with long descriptions, wired into the examples settings. - DetailPage.test.tsx: guards the wrapping/right-alignment/CSS contract with the 14-action fixture. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… + 1.11.1 When the legacy admin refuses to be framed (Django's XFrameOptionsMiddleware sends `X-Frame-Options: DENY`, or a cross-origin `frame-ancestors` block), the browser painted its broken-image glyph and no reliable `error` event fired on the iframe. - LegacyIframe now runs a `loading → loaded → refused` state machine: `onLoad` marks the frame loaded; a ~4s timeout with no `onLoad` marks it `refused` and swaps in an explicit "Embedding refused by the legacy admin — open in new tab" fallback (keeps the proven-working Open-in-new-tab button and the #665 same-origin validation + sandbox). - LegacyIframe.test.tsx: covers same src as the link, loaded (no flip), refused→fallback on timeout, no-flip-before-timeout, and off-origin rejection (with fake timers). - README: documents required backend headers — `X-Frame-Options: SAMEORIGIN` (or removing XFrameOptionsMiddleware); cross-origin `Content-Security-Policy: frame-ancestors <spa-origin>` plus `SESSION_COOKIE_SAMESITE = "None"` + `SESSION_COOKIE_SECURE`. The examples/jobs `?run_custom=1` variant exercises the path end-to-end. - Bump 1.11.0 → 1.11.1 + CHANGELOG [1.11.1] (Fixed: #672, #673). Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Two detail-page regressions shipped in 1.10.0/1.11.0, plus version bump to 1.11.1.
#672 — Detail-page header toolbar overflow
Root cause: the header already stacked breadcrumb / title / toolbar as three full-width rows (#658/#674), but the content column
<main>is a flex item whose defaultmin-width: autorefuses to shrink below the intrinsic width of its widest content. A ModelAdmin with 8+ actions made that intrinsic width exceed the viewport, soflex-1blew<main>past the viewport edge and dragged every stacked row — title and breadcrumb included — off-screen. No amount of header re-stacking could fix it because the rows were full-width of an over-widemain.Fix:
<main>getsmin-w-0so it shrinks to the viewport and the toolbar'sflex-wrapactually reflows.w-full min-w-0 flex-wrap; Edit/Delete cluster stays right-aligned (ml-auto) on the last line regardless of action count.whitespace-normal break-words) instead of forming a wide min-content box.examples/many_actionsPipelineAdminfixture: 12 batch + 2 detail-only actions with long descriptions, wired into the examples settings.DetailPage.test.tsxguards: all 14 actions render, full-width wrapping row separate from title/breadcrumb, Edit/Delete trailing-cluster after every action, long labels wrap.#673 — Legacy-iframe broken-image → graceful refused fallback
Root cause: the legacy admin response carries
X-Frame-Options: DENY(Django'sXFrameOptionsMiddlewaredefault), so the browser refuses to frame it and paints its broken-image glyph — with no reliableerrorevent on the iframe.Fix:
LegacyIframeruns aloading → loaded → refusedstate machine:onLoadmarks loaded; a ~4s timeout with noonLoadmarksrefusedand swaps in an explicit "Embedding refused by the legacy admin — open in new tab" fallback. Keeps the proven Open-in-new-tab button and the [security] Validate legacy_url and sandbox the legacy iframe (#659) before using it as src/href #665 same-origin validation +sandbox. The iframesrcis verified identical to the Open-in-new-tab href.LegacyIframe.test.tsx: same-src, loaded (no flip), refused→fallback on timeout, no-flip-before-timeout, off-origin rejection — with fake timers.X-Frame-Options: SAMEORIGIN(or removingXFrameOptionsMiddleware); cross-originContent-Security-Policy: frame-ancestors <spa-origin>plusSESSION_COOKIE_SAMESITE = "None"+SESSION_COOKIE_SECURE.examples/jobs?run_custom=1variant exercises the iframe path end-to-end.Follow-up (not in scope): an optional server-side
legacy_iframeableflag (cross-repo, rest-api / mcp-api) could switch to the "open in new tab only" UI immediately rather than after the timeout. Client-side detection + docs + fixtures is the MVP here.Verification
pnpm test257 passed (36 files),pnpm typecheckclean,pnpm lint:js/pnpm lint:cssclean,pnpm buildOK.ruff check django_admin_react testspass,mypy django_admin_reactclean (9 files),bandit -r django_admin_react0 issues,pytest -q75 passed.ruff format --checkflags only pre-existing-on-main example files (fintech/library/manage.py/settings.py); the newmany_actionsfiles are formatted.manage.py makemigrations --check many_actions→ no changes detected.Closes #672
Closes #673
🤖 Generated with Claude Code