first pass of merging in warp (doesn't build)
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
# Shared session QR code — Product Spec
|
||||
Figma: https://www.figma.com/design/CsBdBW4YoLgSAbr5eSkwV6/House-of-Agents?node-id=7877-43943&t=F8mi5pp4M5ch1kmN-1
|
||||
## Summary
|
||||
Users sharing a live Warp session should be able to show a QR code for the same session URL that appears in the live-session sharing dialog. The QR code makes it easy for someone nearby to join from another device without manually copying or typing the link.
|
||||
## Problem
|
||||
The live-session sharing dialog already exposes invite controls, access controls, the canonical session URL, and a `Copy link` action. That works for digital sharing, but it is awkward during demos, in-person collaboration, and mobile handoff flows where scanning a QR code is faster than sending a link.
|
||||
## Goals
|
||||
- Add a QR-code entry point to the existing live-session sharing dialog.
|
||||
- Add a `View QR code` call to action to the `Remote control link copied.` toast shown immediately after a shared session starts.
|
||||
- Ensure the QR code represents exactly the same URL shown in the dialog footer.
|
||||
- Let users copy the session link from the QR-code view or download the QR code as a PNG.
|
||||
- Preserve the existing `Copy link` behavior and sharing access controls.
|
||||
## Non-goals
|
||||
- Changing shared-session permission semantics or default access levels.
|
||||
- Creating a separate link format for QR codes.
|
||||
- Adding QR codes for non-session shareable objects such as notebooks, workflows, Warp Drive objects, or AI conversations.
|
||||
- Adding analytics, expiration, or tracking parameters to the QR URL beyond what the canonical session URL already contains.
|
||||
## Behavior
|
||||
1. When Warp starts a shared terminal session and copies the remote-control link automatically, the toast reads `Remote control link copied.` and includes a `View QR code` link.
|
||||
2. Activating `View QR code` opens the QR-code view for that newly-started shared session directly in the pane-header sharing overlay. Warp does not require the user to open the access-management panel first. Once that overlay opens, the toast may expire or be dismissed independently, but that must not dismiss, navigate away from, or otherwise change the QR-code view.
|
||||
3. The QR-code view replaces the sharing dialog's contents inside the same compact overlay. It must not open a separate full-height panel or stretch the overlay to fill the workspace height.
|
||||
4. If the live-session sharing overlay is already open when the toast action is activated, Warp transitions that overlay into the QR-code view instead of creating a second overlay.
|
||||
5. Pressing Back from a toast-opened QR-code view returns to the live-session sharing dialog for the same session.
|
||||
6. When the live-session sharing dialog is open for a shared terminal session and a canonical session URL is available, the footer shows three controls in this order:
|
||||
- A read-only, truncated text field containing the canonical session URL.
|
||||
- An icon-only QR-code button.
|
||||
- The existing `Copy link` button.
|
||||
7. The QR-code button is visually distinct and immediately adjacent to the link field and `Copy link` button, matching the Figma layout. It must read as its own bordered button rather than visually merging into the URL field or disappearing into the footer row, and it does not replace or change `Copy link`.
|
||||
8. The QR-code button shows a tooltip equivalent to `Show QR code`.
|
||||
9. Activating the QR-code button opens a QR-code view in the same sharing overlay. The underlying shared session continues uninterrupted.
|
||||
10. The QR-code view header contains:
|
||||
- A back arrow on the left.
|
||||
- The title `Share session QR code`.
|
||||
- An `ESC` keyboard hint.
|
||||
- A close button.
|
||||
11. Pressing the back arrow returns to the live-session sharing dialog with the previous invite text, access menus, link state, and scroll position preserved as much as the existing dialog architecture allows.
|
||||
12. Pressing Escape or the close button dismisses the sharing overlay, consistent with the existing sharing dialog dismiss behavior.
|
||||
13. The QR-code view renders a scannable QR code for the exact URL shown in the live-session sharing dialog footer at the time the QR view is rendered.
|
||||
14. The QR code is centered in a square card matching the mock:
|
||||
- Dialog width about 400px.
|
||||
- QR card about 192px square.
|
||||
- QR image about 160px square.
|
||||
- High-contrast black modules on a white background with a sufficient quiet zone.
|
||||
15. QR code rendering is intentionally not theme-colored. The code must stay black-on-white in both dark and light themes so phone cameras can scan it reliably.
|
||||
16. If the session URL changes while the QR-code view is open, the QR image updates to encode the latest canonical URL. This includes channel-specific URL changes and preview/staging URL behavior already handled by the session-link generator.
|
||||
17. If the session ends while the QR-code view is open, the QR view keeps encoding the same URL that the sharing dialog would copy for the session. If Warp can no longer provide a session URL, the QR view closes or shows a non-scannable error state instead of displaying stale or empty data.
|
||||
18. Scanning the QR code has the same result as opening the visible session URL directly. Authorization is still enforced by the existing shared-session access controls; QR generation does not grant access by itself.
|
||||
19. The copy icon in the QR-code view copies the same plain session URL as the sharing dialog's existing `Copy link` button, across all supported platforms.
|
||||
20. When the QR-code view copy action succeeds, Warp shows the existing link-copy success feedback.
|
||||
21. The QR-code view copy action does not attempt to write QR image data to the clipboard.
|
||||
22. The download icon in the QR-code view opens the platform save-file flow for a PNG file. The default filename is recognizable as a Warp session QR code and includes the session id when available.
|
||||
23. If the user cancels the save-file flow, Warp leaves the QR-code view open and does not show an error.
|
||||
24. If PNG generation or file writing fails, Warp leaves the QR-code view open and shows a concise failure toast.
|
||||
25. The existing `Copy link` button in the live-session sharing dialog continues to copy the plain session URL and show the existing link-copy success feedback.
|
||||
26. The QR-code view's copy and download icon buttons show tooltips equivalent to `Copy link` and `Download QR code`.
|
||||
27. Multiple shared sessions can each open their own sharing dialog and QR-code view. Each QR code always encodes the URL for that dialog's session target.
|
||||
28. The QR code must not encode additional sensitive data beyond the canonical session URL that the user can already copy from the sharing dialog.
|
||||
@@ -0,0 +1,137 @@
|
||||
# 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:34` defines `ShareableObject::Session`, carrying a terminal view handle, `SessionId`, and `started_at`.
|
||||
- `app/src/drive/sharing/mod.rs:45` implements `ShareableObject::link`; the session branch returns `join_link(session_id)`, which should be the sole source of truth for QR payloads.
|
||||
- `app/src/terminal/shared_session/mod.rs:285` implements `join_link`, including staging/native-intent and preview-channel behavior.
|
||||
- `app/src/terminal/view/shared_session/view_impl.rs:591` sets the pane header shareable object to `ShareableObject::Session` when a share starts and opens the sharing dialog unless the flow explicitly skips it.
|
||||
- `app/src/terminal/view/shared_session/view_impl.rs:408` refreshes the same shareable object from active shared-session state when roles or session state change.
|
||||
- `app/src/workspace/view.rs:4281` listens for `ManagerEvent::StartedShare`, copies the remote-control link, and shows the `Remote control link copied.` toast.
|
||||
- `app/src/terminal/shared_session/manager.rs:135` emits `ManagerEvent::StartedShare` with the new shared-session id and window id.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:69` defines the generic `SharingDialog` state and `UiStateHandles`.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:149` defines `SharingDialogAction`; it currently has `CopyLink` and permission actions but no QR actions.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:332` resets the dialog target through `set_target`.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:368` exposes `has_shared_session_target`, which already distinguishes session targets from other shareable object types.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:428` treats sessions as editable so the sharing dialog opens for session targets.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:905` copies the target URL and sends `CopiedSharedSessionLink` telemetry for session targets.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:2372` renders the footer link and `Copy link` button in `render_object_link`.
|
||||
- `app/src/drive/sharing/dialog/mod.rs:2505` composes the full dialog in `render`.
|
||||
- `app/src/drive/sharing/style.rs:15` contains sharing-dialog layout constants and color helpers.
|
||||
- `crates/warpui_core/src/platform/file_picker.rs:127` defines `SaveFilePickerConfiguration`, and `crates/warpui_core/src/core/view/context.rs:311` exposes `open_save_file_picker` for save-file flows.
|
||||
- `crates/warpui_core/src/clipboard.rs:29` defines `ClipboardContent`, 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, including `Icon::Download`, `Icon::Copy`, and `Icon::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 code`
|
||||
The action should carry the new `SessionId` from `ManagerEvent::StartedShare` and 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::Access`
|
||||
- `SharingDialogMode::QrCode`
|
||||
Store it on `SharingDialog`, defaulting to `Access`. Reset it to `Access` in `set_target` so a recycled dialog does not show QR content for a different target.
|
||||
Extend `UiStateHandles` with mouse states for:
|
||||
- the footer QR button;
|
||||
- the QR dialog back button;
|
||||
- the QR dialog copy-link button;
|
||||
- the QR dialog download button.
|
||||
Extend `SharingDialogAction` with:
|
||||
- `ShowQrCode`
|
||||
- `BackToAccessDialog`
|
||||
- `DownloadQrCode`
|
||||
Keep `SharingDialogAction::Close` behavior 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_link` returns `None`;
|
||||
- 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`:
|
||||
- `Access` renders the existing dialog contents.
|
||||
- `QrCode` renders a compact QR view for session targets.
|
||||
Keep the same outer `Dismiss` behavior 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.target` is no longer a session target or `target_link` is missing, render a compact error body with the same header and a message equivalent to `Unable 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 as `qrcode` in the root `Cargo.toml` and `app/Cargo.toml`. Prefer a dependency that can produce a boolean module matrix without pulling in a large image stack; use the existing workspace `image` crate 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_picker` with `SaveFilePickerConfiguration::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>.png` for session targets. If the session id is unavailable in a future target shape, fall back to `warp-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:
|
||||
- `OpenedSharedSessionQrCode`
|
||||
- `DownloadedSharedSessionQrCode`
|
||||
If 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_link` includes the QR button only for `ShareableObject::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::link` result; 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 `CopyLink` tests 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::dialog`
|
||||
- `cargo nextest run --no-fail-fast --workspace drive::sharing::qr_code`
|
||||
- `cargo check`
|
||||
If the implementation changes Rust files, follow the repository convention of adding tests in a sibling `_tests.rs` file 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_picker` abstraction and hiding/disabling download where local filesystem support is unavailable.
|
||||
- Link drift: mitigate by deriving all QR payloads from `ShareableObject::link` at action/render time rather than storing a separate URL string.
|
||||
Reference in New Issue
Block a user