Repository navigation
Replace the pad QR card's size toggle with a resize handle (128px to 1024px) #465
Description
Summary
The pad QR card (#377) has a size toggle. On hover a blurred veil covers the code, and a centred button doubles it from 128px to 256px. The maintainer asked to replace it:
"I want to remove that feature, and add another similar feature, the feature I want is the indicator that display (show up) when user mouse hover the qrcode and let the user to resize freely. so let's run team of UI and UX and see what is the best solution to have this feature. let the user to control the size of qrcode. just remember to add a minimum and maximume size."
This issue replaces the toggle with one resize handle on the card's bottom-left corner. It changes the size part of the #377 rulings of 2026-10-05. The position, room and chat-bound rulings stay. The maintainer ruling of 2026-10-07 settles the handle look, hover, range, click and reset.
Where
Paths are under apps/webapp/src/ unless shown in full.
apps/webapp/src/components/pages/document/components/PadQrCode.tsx(52 lines)::17-18:isLargestate.:34-37:w-32orw-[clamp(8rem,calc(100cqh-5.25rem),16rem)], with a--motion-panelwidth tween.:40-48: thedata-testid="pad-qr-size"button witharia-pressedand<QrVeil>.:24-27: the size container above--chat-panel-height, thetop-3.5/ Find placement, and the13.5remshow gate. These stay.
apps/webapp/src/components/ui/QrVeil.tsxstays. The Share dialog's Present button uses it (components/TipTap/pad-title-section/PresentQrCode.tsx:75-77).apps/webapp/src/components/icons/registry.ts:68(theLuMinimize2import) and:197(minimize2): used only by this card, so both go.maximize2stays, because chat media uses it.apps/webapp/cypress/e2e/document/pad-qr-toggle.cy.ts:87-103tests the toggle.- Precedents to copy, not import:
components/chatroom/hooks/useChatPaneGrabber.ts:35-66: element-local pointer handlers withsetPointerCapture.components/pages/document/components/chat/ChatPane.tsx:21-45:role="slider"with Arrow, Home and End keys.
Design
Remove. Delete the veil button, isLarge, the QrVeil import and Icons.minimize2. At rest nothing covers the code, as today. The card body has no click action.
Handle
-
One handle on the bottom-left corner. The card is pinned top-right, so it grows down and left. The code stays square.
-
Put it inside the
pad-qr-codewrapper, as a sibling of therole="img"panel. The panel isoverflow-hidden, so a child would be clipped. -
The hit area is at least 24×24px (WCAG 2.5.8), centred on the corner. It is transparent, so it may lie over modules without hurting a scan.
-
The visible grip (option A, ruled 2026-10-07) is two short parallel diagonal strokes in
--qr-ink, like a mirrored text-area resize grip. Draw it as an inlinearia-hidden<svg>inPadQrCode.tsx; add noIconsentry. The strokes stay inside the white quiet zone and inside the panel'srounded-boxcorner, and never touch a QR module. At 128px the quiet zone is about 12.5px (read from a mock, not measured). -
Shown with opacity, never
display: none, so keyboard focus can reveal it:- when the card is hovered;
- when the handle has focus;
- during a drag.
Hover is a deliberate exception to the parity rule (ruling 2026-10-07): the card cannot be selected. Fade with
--motion-overlay-inandmotion-reduce:transition-none. -
Cursor:
nesw-resize. During a drag, the document body also gets that cursor anduser-select: none, as the TOC sash does.
Drag
- On a primary-button
pointerdown:preventDefault(),setPointerCapture, and record the start point and the start size (the painted size, see Bounds). The handle hastouch-action: none. - On move:
dx = startX - clientXanddy = clientY - startY.deltais whichever has the larger absolute value, with its sign, so a drag right or up alone also shrinks. Thensize = clamp(MIN, start + delta, cap), rounded to whole px. - Resize live. Turn the width tween off during the drag.
pointerupandpointercancelboth end the drag, as the chat grabber does (ChatPane.tsx:32-33). Clear the body cursor anduser-selecton drag end and on unmount, as the TOC sash does (components/pages/document/hooks/useTocResize.tsx:110-113,:298). A Private seal or a pad switch can unmount the card mid-drag.
No-drag paths (WCAG 2.5.7 and 2.1.1)
- Click. A press is a click when neither
|dx|nor|dy|reaches 8px, as in the chat grabber (TAP_PX,useChatPaneGrabber.ts:12,:55). Decide it in thepointeruphandler, as the grabber does (:75-78), with noonClick. When the painted size isMIN, it goes to the cap. Otherwise it goes back toMIN. - Keys.
role="slider",tabIndex={0},aria-label="Resize QR code",aria-valuemin={MIN},aria-valuemax={MAX},aria-valuenow,aria-valuetext="{n} pixels".- ArrowUp and ArrowRight grow by 16px. ArrowDown and ArrowLeft shrink by 16px.
- Home goes to
MIN, End goes to the cap. - Keys and clicks keep the
--motion-panelwidth tween (motion-safe).
- Focus ring:
focus-visible:ring-2 focus-visible:ring-primary focus-visible:outline-none.
Bounds
MIN = 128(8rem), the default and today's size.MAX = 636, ruled by the maintainer. Both are module constants inPadQrCode.tsx.cap = Math.min(MAX, container.clientHeight - 84).containeris a ref on the size container (:24); 84px is5.25rem, so say so in a comment. Read it on each pointerdown, key press and click. Drag, keys and click start from the painted size,Math.min(size, cap).QrCodetakes nostyleprop, so set the width through a CSS variable. Putstyle={{ '--pad-qr-size':${size}px} as React.CSSProperties}on therole="img"panel, and giveQrCodethe classw-[min(var(--pad-qr-size),calc(100cqh-5.25rem))]. Add no prop toQrCode. The old16remceiling goes away. When docked chat grows or Find opens, the painted code shrinks; when room returns, it grows back to the chosen size.- At large sizes the card may cover the page (ruling 2026-10-07). Do not add a width gate.
- The
13.5remshow gate is unchanged. IfMINever changes, the gate moves with it.
State. One useState<number> in the card. Hiding the card unmounts it, so the size resets to 128, as #377 ruled. No store and no localStorage. All of it stays in PadQrCode.tsx: no new hook, no ui/ file and no export. Do not reuse ui/ResizeHandle.tsx; it is a mouse-only edge sash with role="separator" and no value.
Out of scope
- The Share dialog's QR and Present view.
- Persisting the size.
- Changing the toolbar toggle (
components/TipTap/toolbar/desktop/EditorToolbar.tsx:340-348) or the card position.
Acceptance criteria
- At rest nothing covers the code. Hover or keyboard focus shows the handle on the bottom-left corner. The veil button is gone.
- A drag resizes the code live and keeps it square. It stops at 128px and at 636px, or earlier at the room cap above docked chat. A drag right or up alone shrinks it.
- A drag does not select text in the editor and does not move the caret.
- A click on the handle switches between 128px and the cap.
- Tab reaches the handle. Arrow keys step by 16px, and Home and End go to the limits. VoiceOver in Chrome on macOS reads the handle as "Resize QR code", a slider, with the value "128 pixels".
- The handle and its focus ring are visible in light, dark and
docsplus-dark-hc. - At 128px, with a 30-character slug, the grip strokes stay inside the white quiet zone and touch no QR module.
- Opening docked chat or Find shrinks a large code to fit, and closing them restores it.
- Hiding and showing the card resets it to 128px.
- In
pad-qr-toggle.cy.ts, the size block (:87-103) is replaced, and the test title at:77no longer says "doubles on demand". At the spec's 1680×1050 viewport: hover shows the handle;realMouseDown,realMouseMoveandrealMouseUpdrag it down and left past the limit, and the code width reachesmin(636, cap); onerealClickon the handle returns it to 128px. It is observed green locally (the spec is local-only). Keys stay a browser check. -
apps/webapp/CLAUDE.md§Pad Workspace Surfaces ("Pad QR card") and the[Unreleased]QR bullet inapps/webapp/CHANGELOG.mddescribe the handle. -
.cursor/docs/design-system.md§ShareModalpresent tag(line 954 today) drops the sentence "The pad QR card (Show a QR code for the pad from a desktop toolbar toggle #377) reuses the sameQrVeilwith its size icon." TheQR coderow (:953) stays as it is: the Share card passesquietZone={2}(PresentQrCode.tsx:76). The same section records the pad card's grip: two diagonal--qr-inkstrokes in the bottom-left quiet zone, shown on hover, focus and drag. That file is locked, so the lock hook asks the maintainer.
Activity
HMarzban commentedon Oct 7, 2026
Maintainer ruling, 2026-10-07
This changes the size part of the #377 rulings of 2026-10-05.
.cursor/docs/design-system.mdare approved.feat(webapp): resize the pad QR card from a corner handle
fix(webapp): let the pad QR code grow to the room and keep a thin margin
HMarzban commentedon Oct 12, 2026
Shipped, 2026-10-10
Deployed in run 38068638114 (
e5e210d02, green).8ac7a9089: the size toggle is replaced by a corner resize handle.03ea0648a: maintainer ruling. The largest size is now 1024px, not 636px, so the room is the limit. The margin is a--qr-platepadding ofclamp(12px, 4% of size, 24px).228d96114: the drag state is React state. The body cursor and text selection are cleared on drag end and on unmount.cypress/e2e/document/pad-qr-toggle.cy.tspasses 4/4. It was proven by sabotage: a 636px cap fails it. The title now says 1024px.[-]Replace the pad QR card's size toggle with a resize handle (128px to 636px)[/-][+]Replace the pad QR card's size toggle with a resize handle (128px to 1024px)[/+]