13 KiB
Shared session QR code — Tech Spec
Product spec: specs/qr-code/PRODUCT.md
Context
PRODUCT.md defines the user-visible behavior for adding a QR-code affordance to the live-session sharing dialog. The implementation should extend the existing session sharing dialog rather than adding another sharing surface.
The relevant current code paths are:
app/src/drive/sharing/mod.rs:34definesShareableObject::Session, carrying a terminal view handle,SessionId, andstarted_at.app/src/drive/sharing/mod.rs:45implementsShareableObject::link; the session branch returnsjoin_link(session_id), which should be the sole source of truth for QR payloads.app/src/terminal/shared_session/mod.rs:285implementsjoin_link, including staging/native-intent and preview-channel behavior.app/src/terminal/view/shared_session/view_impl.rs:591sets the pane header shareable object toShareableObject::Sessionwhen a share starts and opens the sharing dialog unless the flow explicitly skips it.app/src/terminal/view/shared_session/view_impl.rs:408refreshes the same shareable object from active shared-session state when roles or session state change.app/src/workspace/view.rs:4281listens forManagerEvent::StartedShare, copies the remote-control link, and shows theRemote control link copied.toast.app/src/terminal/shared_session/manager.rs:135emitsManagerEvent::StartedSharewith the new shared-session id and window id.app/src/drive/sharing/dialog/mod.rs:69defines the genericSharingDialogstate andUiStateHandles.app/src/drive/sharing/dialog/mod.rs:149definesSharingDialogAction; it currently hasCopyLinkand permission actions but no QR actions.app/src/drive/sharing/dialog/mod.rs:332resets the dialog target throughset_target.app/src/drive/sharing/dialog/mod.rs:368exposeshas_shared_session_target, which already distinguishes session targets from other shareable object types.app/src/drive/sharing/dialog/mod.rs:428treats sessions as editable so the sharing dialog opens for session targets.app/src/drive/sharing/dialog/mod.rs:905copies the target URL and sendsCopiedSharedSessionLinktelemetry for session targets.app/src/drive/sharing/dialog/mod.rs:2372renders the footer link andCopy linkbutton inrender_object_link.app/src/drive/sharing/dialog/mod.rs:2505composes the full dialog inrender.app/src/drive/sharing/style.rs:15contains sharing-dialog layout constants and color helpers.crates/warpui_core/src/platform/file_picker.rs:127definesSaveFilePickerConfiguration, andcrates/warpui_core/src/core/view/context.rs:311exposesopen_save_file_pickerfor save-file flows.crates/warpui_core/src/clipboard.rs:29definesClipboardContent, which the existing link-copy action already uses for plain-text URLs. The app icon inventory now exposes the QR flow controls used by this surface, includingIcon::Download,Icon::Copy, andIcon::QrCode, with the QR asset mapped alongside the other share/link icons.
Proposed changes
1. Add the toast entry point shown in the mocks
Extend the existing shared-session start toast in app/src/workspace/view.rs so it renders:
- message:
Remote control link copied. - inline action:
View QR codeThe action should carry the newSessionIdfromManagerEvent::StartedShareand route back to the matching shared terminal view. Add a workspace action dedicated to opening the QR flow for that session id instead of relying on whichever pane is currently focused. Add a lookup helper on the shared-session manager that resolves a shared session id back to its terminal view. From there, dispatch into the terminal/pane-header sharing flow so the right pane opens its existing sharing overlay in QR mode. If the standard live-session sharing dialog is already open, switch that existing overlay into QR mode. If it is closed, open it directly in QR mode. Back should always return to the access-management panel for the same session target.
2. Add QR mode to SharingDialog
Add a dialog mode enum in app/src/drive/sharing/dialog/mod.rs, for example:
SharingDialogMode::AccessSharingDialogMode::QrCodeStore it onSharingDialog, defaulting toAccess. Reset it toAccessinset_targetso a recycled dialog does not show QR content for a different target. ExtendUiStateHandleswith mouse states for:- the footer QR button;
- the QR dialog back button;
- the QR dialog copy-link button;
- the QR dialog download button.
Extend
SharingDialogActionwith: ShowQrCodeBackToAccessDialogDownloadQrCodeKeepSharingDialogAction::Closebehavior unchanged: it should still close the overlay and reset editable state.
3. Render the QR entry point only for session targets
Extract a small helper such as target_link(&self, app: &AppContext) -> Option<String> so render_object_link, copy, and QR actions all use the same URL lookup.
Modify render_object_link so that when self.target is Some(ShareableObject::Session { .. }), the footer row includes an icon-only QR button between the link field and the existing Copy link button. For non-session targets, keep the current link + copy button layout.
The QR button should:
- use the same button/style system as the existing footer controls;
- dispatch
SharingDialogAction::ShowQrCode; - be disabled or omitted when
target_linkreturnsNone; - show a tooltip equivalent to
Show QR code. Add a bundled QR icon if needed: - Add the SVG asset under the existing bundled SVG asset location.
- Add a variant such as
Icon::QrCode. - Map it to the bundled path near the other share/link icons.
4. Render the QR-code view inside SharingDialog
In View for SharingDialog, branch on self.mode:
Accessrenders the existing dialog contents.QrCoderenders a compact QR view for session targets. Keep the same outerDismissbehavior and border/background styling so the QR view remains part of the pane-header sharing overlay. Use the Figma dimensions as targets rather than introducing a new modal system:- width around 400px;
- header height around 48px;
- body centered around a 192px QR card;
- two 32px icon buttons beneath the QR card. The QR branch must preserve the sharing dialog's compact intrinsic overlay layout. Render it with minimum-height column sizing rather than max-height stretching so opening QR mode replaces the dialog contents instead of producing a workspace-height panel. The QR view should render:
- a header row with back button,
Share session QR code,ESC, and close button; - a QR code card;
- copy-link and download-image icon buttons.
If
self.targetis no longer a session target ortarget_linkis missing, render a compact error body with the same header and a message equivalent toUnable to create QR code for this session link.
5. Generate QR data with a small pure helper
Add a small QR helper module, for example app/src/drive/sharing/qr_code.rs, with pure functions:
qr_matrix_for_url(url: &str) -> Result<QrMatrix, QrCodeError>qr_png_for_url(url: &str, pixel_size: u32) -> Result<Vec<u8>, QrCodeError>Add a workspace dependency on a QR encoder crate such asqrcodein the rootCargo.tomlandapp/Cargo.toml. Prefer a dependency that can produce a boolean module matrix without pulling in a large image stack; use the existing workspaceimagecrate for PNG encoding because it is already present. Use the same QR helper for on-screen rendering and PNG generation so scanning behavior cannot drift between the two paths. For on-screen rendering, prefer drawing the QR matrix directly with Warp UI rectangles rather than feeding a generated PNG back through the image cache. This keeps the view deterministic, avoids temporary files, and makes sizing straightforward. The helper should expose module count and module values; the view computes cell size and quiet-zone padding inside the 160px visual target. For PNG export, generate a black-on-white PNG with a quiet zone. Use a larger export size than the on-screen display, such as 512px or 1024px, so downloaded images remain scannable when printed or projected.
6. Copy the session link from QR mode
Wire the QR dialog's copy button to the existing SharingDialogAction::CopyLink behavior so it:
- resolves
target_link; - writes the same plain session URL that the access dialog footer copies;
- preserves the existing shared-session link-copy telemetry and toast feedback;
- behaves consistently across platforms without depending on image clipboard support.
7. Download QR image
Implement SharingDialogAction::DownloadQrCode by:
- generating the same PNG bytes;
- opening
ctx.open_save_file_pickerwithSaveFilePickerConfiguration::new().with_default_filename(default_qr_filename(...)); - writing the bytes to the selected path on the background executor or through an existing async file-write helper;
- showing a success toast when the file is written;
- showing a failure toast on write or generation errors;
- doing nothing when the picker returns
None. Default filename suggestion:warp-session-qr-code-<session-id>.pngfor session targets. If the session id is unavailable in a future target shape, fall back towarp-session-qr-code.png. The download action can be compiled only for local filesystem builds if needed. If save-file picker or filesystem writes are unavailable for a target platform, disable or hide the download button there rather than showing a broken control.
8. Preserve existing sharing behavior
Do not change:
ShareableObject::Session.link;join_link;SharingDialogAction::CopyLink;- ACL update actions;
- session invite flow;
- pane-header sharing dialog toggling. The QR flow should be additive. Existing tests for session permissions and link-copy behavior should keep passing without expected-output changes except where snapshots explicitly include the new QR button.
9. Telemetry
Keep existing link-copy telemetry unchanged. Add telemetry only if the product/event taxonomy already has an appropriate place for it:
OpenedSharedSessionQrCodeDownloadedSharedSessionQrCodeIf new telemetry is added, include the same action source where available or derive it from the sharing dialog context. Avoid error-level logs for QR generation or export failures; use user-visible toasts and at most warn-level diagnostic logging.
Testing and validation
Map validation to PRODUCT.md behavior:
- Behavior 1-5: workspace/session tests verify the shared-session toast includes
View QR code, that it targets the newly-started session id, that clicking it opens QR mode on the correct pane even when the access dialog was not already open, and that the QR state remains the same compact sharing overlay rather than a full-height panel. - Behavior 5-7: unit or view tests verify that
render_object_linkincludes the QR button only forShareableObject::Session, and non-session targets still render the existing footer. - Behavior 9-12: view/action tests verify
ShowQrCode,BackToAccessDialog,Close, and Escape transition between access mode, QR mode, and closed overlay as expected. - Behavior 13, 15, 18, 29: pure QR helper tests verify that the generated matrix/PNG encodes the exact URL passed in and does not add extra query parameters or payload data.
- Behavior 14: screenshot or integration verification compares the QR view against the Figma layout in dark theme, including header, centered QR card, and copy/download buttons.
- Behavior 16-17: tests update the target/session link while QR mode is active and verify the rendered/exported QR payload follows the current
ShareableObject::linkresult; missing links render the error state. - Behavior 19-21: clipboard tests verify the QR dialog copy button routes through the plain-link copy path and does not attempt image clipboard writes.
- Behavior 22-24: save-file tests cover default filename, cancel behavior, successful write, and write failure.
- Behavior 25: existing
CopyLinktests or new targeted tests verify plain link copying and toast behavior remain unchanged. - Behavior 26: manual verification confirms the QR view copy/download icon buttons show the expected tooltips.
- Behavior 28: unit tests construct two session targets with different ids and verify each QR helper call uses that target's own link. Suggested targeted commands after implementation:
cargo nextest run --no-fail-fast --workspace drive::sharing::dialogcargo nextest run --no-fail-fast --workspace drive::sharing::qr_codecargo checkIf the implementation changes Rust files, follow the repository convention of adding tests in a sibling_tests.rsfile and importing it with a#[cfg(test)]directive from the implementation module.
Parallelization
Do not split this implementation across parallel agents. The work is concentrated in the sharing dialog, icon plumbing, QR generation helper, and adjacent tests; parallel edits would likely collide in the same files and add coordination overhead.
Risks and mitigations
- QR scan reliability: mitigate by keeping black-on-white rendering, including a quiet zone, and validating with an actual phone camera during manual QA.
- Platform clipboard differences: mitigate by reusing the existing plain-link clipboard path shared by supported platforms.
- Platform save-file differences: mitigate by using the existing
open_save_file_pickerabstraction and hiding/disabling download where local filesystem support is unavailable. - Link drift: mitigate by deriving all QR payloads from
ShareableObject::linkat action/render time rather than storing a separate URL string.