Editing a CV should show the CV: put the preview on the page, not in another tab #293
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Postulo/postulo#293
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Editing a CV and seeing a CV are two tabs and a manual refresh. The preview should be on the page you are editing.
What the loop is today
templates/documents/cv_detail.htmlis where a CV is actually built: the entries in order, the arrows that move them, the link to edit one, the box that adds more. Every one of those is a plain form post that redirects back, and the page carries no htmx at all —hx-appears zero times in it.The preview is a button at
:16-17:So the loop is: move an entry, wait for the page to reload, switch tab, reload that, look, switch back. Judging a document's shape — whether the summary is too long, whether a section broke badly, whether the thing fits a page — is the one task where the answer must be visible while the change is made, and it is the one place Postulo makes you leave the page to get it.
Everything needed already exists.
CVPreviewView(documents/views.py:209) renders the CV exactly as the PDF renderer sees it, which is the property that makes an in-page preview worth having rather than approximate.Why a frame, and the two traps in it
The preview is a whole document, not a fragment.
templates/documents/themes/base_cv.htmlbegins<!doctype html>and carries the theme's entire stylesheet inlined in a<style>, because the renderer must fetch nothing. It cannot be dropped into the editing page: its stylesheet would land on the application's own. A frame is the only vehicle that keeps two documents apart, and there is no<iframe>anywhere in Postulo today.Trap one: the content security policy blocks frames outright. The policy sets
default-src 'none'and names noframe-srcorchild-src(config/settings/base.py:582-600), so frames fall back to'none'and even a same-origin<iframe src="…/preview/">is refused. It fails silently — a blank box — and since #232 every browser test fails on a policy refusal, so this will be discovered as a red suite rather than a blank frame. The fix is deliberate: addframe-src 'self'and say in the comment beside it why a same-origin frame is now wanted.frame-ancestors 'none'is untouched and stays: it governs who may frame Postulo, not what Postulo may frame.Trap two: it must be
src, neversrcdoc. The preview's<style>carries a per-request nonce (#232), and the header names that nonce on that response alone. Asrcdocframe inherits the parent document's policy, where the nonce does not match, so the preview would render unstyled — which is exactly the bug #232 just fixed in production. Fetching the real address keeps the response, its header and its nonce together.The shape
The version that needs no JavaScript is the whole feature. Every edit on this page is already a full page load, so a frame whose
srcis the preview address re-renders on every save by itself. No htmx, no polling, no refresh button: move an entry, the page comes back, the preview beside it is current. That is the first commit, and it is most of the value.What is worth deciding after that:
style— the policy forbids the attribute, which is why stored column widths already go through the CSSOM. So a class or a custom property set in the stylesheet, not markup.letter_detail.html:41). It should get the same treatment, and whether that is this issue or its sibling is worth saying out loud rather than letting it drift.Accessibility, which is not free here
titlenaming what is in it, or a screen reader announces an unlabelled frame.tests/e2e/test_keyboard_and_focus.pyis the place to prove.tests/e2e/test_reflow.pywalks this page in English, Greek and German. A fixed-width frame is precisely what that catches, and a scaled one must still not push the page sideways.tests/e2e/test_accessibility.pyexempts/preview/from axe only (AXE_EXEMPT_SUFFIXES), because what axe would be reading there is a print document. Once a preview is framed inside an application page, axe reads the outer page — so the exemption still holds for the frame's contents and does not excuse the page around it.app.css's@media printblock already hides the chrome; the frame needs a line there.Also worth knowing
render_cv_htmlbuilds the sections and runs the theme template under a language override. A preview beside every save is one extra render per save, which is cheap; a preview that re-renders per keystroke is not, and is the reason the first pass shows what is saved.Done when
frame-src 'self'is in the policy with its reason written beside it, andtests/security/test_requests.pyasserts it.