SPEC-STV-13-Mobile-Gestures
SPEC-STV-13 · Spec header. Spec ID: SPEC-STV-13 · Title: Mobile Gestures & Text Selection Editor · Version: 1.0.0 · Status: Planned · Authority: Specification · Priority: P0 · Owner role: Frontend lead (mobile) · Reviewers: Product architect, AI workflow lead, Security lead, Accessibility lead · Last reviewed: 2026-05-11 · Sync targets: docs/MOBILE_GESTURES_AND_TEXT_SELECTION_EDITOR.md, resources/js/editor/mobile/**, resources/js/ai-selection/mobile/**, .cursor/rules/mobile.mdc · Depends on: SPEC-STV-HUB, SPEC-STV-04, SPEC-STV-08, SPEC-STV-10, SPEC-STV-11 · Consumed by: SPEC-STV-03 (API mobile contract), SPEC-STV-12 (tests / docs / changelog) · Conflict rule: When this spec contradicts SPEC-STV-10 on a mobile-only concern, this spec wins (it is the Source of Truth for mobile editing UX). For desktop-only conflicts, SPEC-STV-04 / SPEC-STV-10 win. The hub is canonical above both. · Change policy: Frontend lead (mobile) + Product architect + Accessibility lead; Registry version bump in SPEC-STV-HUB §10.
1 · Purpose
This document is the Source of Truth for the mobile editing experience in StevoApp. It defines:
- mobile swipe and touch gestures (navigation + block-level),
- text selection behavior on mobile,
- block editing on mobile,
- the mobile slash command menu,
- the select-text → AI Improve / Rewrite / Summarize flow,
- the mobile floating toolbar,
- the mobile AI preview bottom sheet,
- mobile page navigation,
- haptics and feedback,
- accessibility,
- editor safety rules,
- gesture conflict resolution.
All downstream mobile UX must follow this spec. Where SPEC-STV-10 (Mobile & Desktop UX) and this spec overlap, this spec is authoritative for mobile-only behavior; SPEC-STV-10 is authoritative for breakpoint / layout / performance budgets.
2 · Mobile UX principles
- Mobile editing must feel fast, clean, and native.
- Reading must be easier than editing — viewers should not be forced into editor chrome.
- Editing must never feel cramped. The composer always has air.
- All critical actions must be reversible. Hard deletes require explicit confirm; soft deletes show an undo snackbar for 6 seconds.
- Accidental deletes must be prevented. No swipe gesture maps to a destructive action without confirmation.
- Gestures must not conflict with browser or native OS gestures (iOS back-swipe, Chrome pull-to-refresh).
- Text selection must be precise and reliable. Native selection handles are not hidden by app chrome.
- AI must never replace text without user approval. The Preview Gate is mandatory (SPEC-STV-08 §3).
- Public pages must be beautiful and readable on mobile.
- Desktop power features must be simplified on mobile, not removed.
3 · Mobile navigation gestures
3.1 Swipe from left edge → Page tree drawer
- Trigger: horizontal swipe starting within 20 px of the left edge, threshold 60 px, velocity ≥ 0.3 px/ms.
- Opens the page tree drawer (workspace switcher + search + recent + favorites + tree).
- Closing: swipe back left, tap scrim, or
Escfrom a hardware keyboard.
- Visible alternative: workspace icon in the top bar opens the drawer with a tap.
3.2 Swipe from right edge → Page context panel
Contents in order: page info, comments, activity, outline (TOC), share settings, AI / RAG status. Same threshold rules as 3.1. Visible alternative: ⋯ menu → “Context”.
3.3 Swipe down on page header → Quick page actions
Reveals a sheet with: search in page, duplicate, share, export, page settings. Visible alternative: the ⋯ menu in the top bar.
3.4 Pull to refresh
On page top, pull down past 80 px triggers a server-reconcile (re-fetch page metadata, latest block deltas, presence). Cooldown 3 s. Does not reload the JS app shell.
3.5 Swipe between pages (optional, opt-in)
Opt-in in settings (gesture.swipe_between_pages = true). When enabled, a two-finger horizontal swipe inside the editor navigates to the previous / next recently visited page. Default OFF to avoid conflict with text selection.
3.6 Long press page in tree → Page actions
Shows: rename, duplicate, move, archive, copy link, public share. Always reachable via the ⋯ icon next to the page row.
3.7 Conflict rule (carry-through)
While the user has an active text selection in the editor, all navigation gestures are suppressed until the selection collapses or the floating toolbar is dismissed. See §14.
4 · Mobile block gestures
4.1 Tap block → Focus
Moves caret into the block. Selection mode is collapsed (no toolbar yet).
4.2 Long press block → Action menu
Duration 350 ms. Shows a bottom action sheet:
- AI Improve · AI Rewrite · AI Summarize
- Duplicate · Move · Convert type
- Comment · Copy block link
- Delete (with confirmation)
4.3 Drag handle
Visible only when the block is focused or after long-press. The handle is a 44×44 px tap target. Drag enters reorder mode; insertion point is shown as a hairline above/below sibling blocks.
4.4 Swipe left on block → Quick actions
Reveals: comment, duplicate, delete. Delete shows an undo snackbar for 6 s before commit.
4.5 Swipe right on block → Tools
Reveals: move, convert, AI tools.
4.6 Two-finger tap → Undo
Undoes last edit. Equivalent to ⌘/Ctrl + Z on desktop.
4.7 Three-finger tap → Redo / Undo menu
Opens a small menu with Undo, Redo, and history list (last 10 changes).
4.8 Drag block
Long-press, then drag. Insertion indicator shows. Drop fires one POST /api/v1/blocks/{id}/move.
4.9 Rules
- Destructive actions require explicit confirm OR an undo snackbar.
- Block movement autosaves (debounced 500 ms idle, 5 s max).
- All actions must be reachable via touch alone. No hidden desktop-only path.
- Accessibility duplicates of every gesture exist as visible buttons (see §13).
5 · Text selection UX
A high-end mobile text selection system. Triggered when the user drags either selection handle or double-taps a word inside an editable surface (paragraph, heading, list, comment, AI output preview, database text cell, template preview).
5.1 Floating toolbar (above selection)
Shows action chips in order: Improve · Rewrite · Summarize · Copy · Comment · More …
- Position: above the selection by default; below if the selection is near the top viewport edge.
- Never covers the selected text or the native selection handles.
- Repositions above the on-screen keyboard when the keyboard mounts.
- Stays anchored to the selection during scroll until the selection collapses or the toolbar is tapped away.
- Collapses into a compact pill (Improve +
⋯) on screens narrower than 360 px.
- Dismissed on outside tap (single tap on non-selection area) or selection collapse.
- Keyboard-accessible:
Tabcycles chips,Enteractivates.
- Screen-reader: each chip has
aria-label; the toolbar group hasrole=toolbar.
5.2 Where selection works
- Page editor (all editable block types).
- Comments (in the comment composer and existing comment bodies).
- Public docs read-only mode (with restricted toolbar; see 5.3).
- AI output previews (read-only with restricted toolbar).
- Database text cells (single-line and multiline).
- Template previews (read-only).
5.3 Read-only surfaces (public page, AI preview, template preview)
The toolbar shows only: Copy · Insert into new page · Save as note (and Comment if the surface allows comments). Replace is disabled. AI actions are allowed only if the viewer is authenticated and has ai:transform ability for the workspace; otherwise hidden.
5.4 Editable surfaces
Apply actions after AI run: Replace selection · Insert below · Copy · Discard.
6 · AI select-to-edit on mobile
Flow:
- User selects text (§5).
- Floating toolbar appears.
- User taps Improve / Rewrite / Summarize (or More … for the full SPEC-STV-08 action set).
- The AI Preview Bottom Sheet opens (§7).
- Result streams in real time over SSE (
POST /api/v1/ai/transform/stream).
- User taps one of: Replace selection · Insert below · Copy · Try again · Discard.
Rules (mandatory):
- AI never auto-applies. Apply is gated on a final
finalSSE event.
- The original selection range is captured at step 1 and restorable until the user resolves the preview.
- Token / cost debug shown only when
settings.dev_mode = truefor the user.
- Every call writes one
ai_text_transformationsrow (SPEC-STV-08 §8).applied = trueonly on Replace / Insert below.
- Respect workspace
ai_settings.enabled(off → toolbar AI chips hidden).
- Respect
rag.enabled(off → the request is sent withuse_kb: false).
- Rate-limited per SPEC-STV-03 §12 (30 / min / user; 500 / day / workspace).
- If the AI call fails, the original text is untouched and the sheet shows an error state with Try again and Discard.
7 · Mobile AI preview bottom sheet
7.1 States
- Compact (default) — 25–40% viewport, action chips visible.
- Half — 50–60% viewport; full result visible with scroll.
- Full — 100% viewport minus status bar; for long results or side-by-side compare.
Transitions via drag on the handle or programmatic on first final event (auto-expand to Half when result exceeds 6 lines).
7.2 Contents (top to bottom)
- Drag handle (32×4 px pill).
- Action name and target action chip (e.g. “Improve”).
- Selected text preview (collapsible, max 3 lines initially).
- Streaming AI result (live; cursor caret blinks at end while streaming).
- Actions: Replace selection · Insert below · Copy · Try again · Discard.
- Footer: model name, tokens (dev mode only), feedback affordance (👍 / 👎).
7.3 Gestures
- Swipe down on handle → minimize one step (Full → Half → Compact → dismissed).
- Second swipe-down from Compact → confirmation
Discard AI result?.
- Drag handle long-press → quick switch between Compact / Half / Full.
- Haptic feedback on Apply (light tap).
- Loading skeleton until first
deltaevent arrives.
- Error state with retry banner if SSE fails or 4xx/5xx returned.
7.4 Safety
- If the user navigates away with an unresolved preview, the result is saved as a draft (
ai_text_transformations.applied = false,draft_at = now) and the sheet can be re-opened from the page menu → “AI draft”.
- Never lose the original text. The selection range is persisted client-side until Discard.
- Never apply partial streamed results — Apply is enabled only after the
finalevent.
8 · Mobile slash command menu
Triggered by typing / at the start of an empty block, or by tapping the + button in the bottom action bar.
8.1 Layout
Full-width bottom drawer with a search input at the top, then grouped, scrollable rows. Each row is 56 px tall: icon (24 px) + title + short description (truncated).
8.2 Commands (canonical mobile order)
Basic — Text, Heading 1, Heading 2, Heading 3, Bullet list, Numbered list, Checklist, Quote, Callout, Divider, Code.
Media — Image, File, Table.
Database — Database (inline), Database (linked), Toggle.
AI — AI Improve, AI Rewrite, AI Summarize (operates on the focused block; opens the AI preview bottom sheet §7).
8.3 UX
- Searchable: keystroke filters list with fuzzy match; debounce 80 ms.
- Recently used appears at the top.
- Keyboard-safe: the drawer rises above the keyboard via
visualViewportAPI.
- Selecting a command inserts a new block of that type below the current one (or converts the current block if it is an empty paragraph).
9 · Mobile editor toolbar
Docked above the keyboard, anchored to the bottom safe area when the keyboard is closed.
9.1 Collapsed state
+plus (open slash menu)
Aaformatting (open expanded toolbar)
- AI (open AI selection actions — enabled only with active selection)
↓keyboard dismiss
9.2 Expanded state (formatting panel)
- Bold · Italic · Underline · Strikethrough
- Inline code · Link
- Heading 1 / 2 / 3 toggles
- Bullet / Numbered / Checklist toggles
- Comment (on selection)
- AI actions (Improve, Rewrite, Summarize, More…)
9.3 Rules
- Toolbar must not overlap the keyboard. Uses
visualViewport.heightto compute offset.
- Works inside iOS Safari (notch + home indicator), Chrome on Android, and PWA standalone mode.
- Desktop toolbar (SPEC-STV-04 §5) and mobile toolbar may differ in layout but must offer the same action set.
10 · Page tree mobile drawer
- Swipe from the left edge (§3.1) or tap the workspace icon to open.
- Header: workspace switcher + search input +
+quick create.
- Sections: Recent (last 10), Favorites, Tree.
- Tree rows: icon, title, chevron to expand children. Nested children are loaded lazily on expand.
- Long press a tree row opens §3.6 actions.
- Drag-reorder ships in v1.1 (off by default).
10.1 Performance
- Lazy load nested pages (one
GET /workspaces/{ws}/pages?parent=…per expand).
- Never load the whole workspace tree.
- Cache recent pages in
IndexedDB(TTL 24 h).
- Skeleton loaders during fetch.
11 · Public mobile pages
Public shared pages (/p/{token}) must be a premium mobile reading surface.
- Fast loading: server-rendered HTML, hydration only for the comment/copy actions if available.
- Clean typography: 17–18 px body, 1.5 line height, single-column.
- Sticky minimal header: workspace icon, page title, share-button.
- Readable tables: horizontal scroll with sticky first column.
- Responsive images:
<picture>+loading="lazy".
- Code blocks: horizontal scroll; copy-code button on tap.
- Table-of-contents drawer accessible via header icon.
- Share button (uses
navigator.sharewhen available).
- OpenGraph metadata: title, description, cover_url, locale.
- No private data leakage. Comments, member lists, activity logs, related pages — none rendered.
- Public pages are read-only. The AI toolbar is disabled unless the visitor is authenticated AND allowed by workspace policy. Tokenized links never grant AI.
- Heavy editor JS bundle is not loaded; a thin reader bundle (≤ 60 KiB gzip) renders the page.
12 · Haptics and feedback
Used sparingly. Web Vibration API where available; native Taptic on iOS.
| Event | Haptic |
|---|---|
| Block moved (drop) | Light tap (10 ms) |
| AI result applied | Light tap |
| Page saved (autosave success) | none (visual only) |
| Public link copied | Light tap |
| Comment added | Light tap |
| Error | Notification pattern (short-pause-short) |
| Destructive action confirm | Warning pattern (medium) |
Do not overuse haptics. Respect OS-level Reduce Motion preferences — disables non-essential haptics.
13 · Accessibility
Every gesture has accessible alternatives.
- Every gesture has a visible button in the bottom action bar, top bar
⋯menu, or block⋮menu.
- Every interactive surface has a screen-reader label (
aria-labeloraria-labelledby).
- Every action has a keyboard equivalent when a hardware keyboard is attached (
⌘/Ctrl + B/I/U,⌘/Ctrl + Kpalette,Tabcycles toolbar chips).
- Minimum tap target 44×44 CSS px.
- High-contrast mode supported (CSS variables; test with
forced-colors: active).
- Reduced motion supported (
prefers-reduced-motion: reducedisables non-essential animations and haptics).
- Do not rely on swipe gestures alone for any important action. Every swipe path also has a tap path.
- Focus management: opening a bottom sheet moves focus to the sheet; closing returns focus to the trigger.
14 · Gesture conflict rules
- Text selection has priority over page navigation. While a selection is active, edge-swipes do not open drawers.
- Block drag has priority only after the drag handle is touched. A regular long-press on the block body opens the action menu, not the drag.
- Browser back gesture must not be hijacked aggressively. Edge-swipes inside the page tree drawer can close the drawer; edge-swipes elsewhere yield to the browser back where the platform mandates.
- Scroll must remain natural. No
touch-action: noneon whole-page elements. Usetouch-action: pan-yon horizontally-scrollable embeds (code, table).
- AI toolbar must not block native selection handles. The floating toolbar's hit area is reduced near the handles; the handles always receive touch first.
- Keyboard shortcuts must not conflict with mobile OS shortcuts. Avoid
⌘+Space,⌘+H, etc.
- Public pages prioritize reading. Most gestures are disabled on public pages except scroll, selection, TOC drawer, share, and copy.
- Editable pages prioritize precise text editing over navigation. Two-finger swipe-between-pages (§3.5) is opt-in and never the default.
15 · Technical implementation notes
15.1 Mobile web
- Editor uses TipTap (ProseMirror) with custom extensions:
MobileSelectionToolbar,MobileSlashMenu,MobileBlockGestures,AiPreviewSheet.
- Use Pointer Events unified API; never mix
touchstartandmousedownhandlers.
touch-action: manipulationon tappable buttons (suppresses 300 ms double-tap zoom).
- Never set
touch-action: noneon text containers — it breaks native selection.
- Bottom sheets instead of desktop modals on mobile breakpoints.
IntersectionObserverto virtualize blocks above 200 per page.
- Autosave debounced (500 ms idle / 5 s max) via
BlockServicebatch endpoint.
- Optimistic UI only for reversible actions; destructive actions wait for server ack.
- Editor content stored as JSON (
page_blocks.content); HTML rendering goes throughSanitizer(SPEC-STV-04 §11, SPEC-STV-11 §9).
- Avoid heavy JS on public pages (§11).
- Use
visualViewportAPI to track keyboard height; reposition toolbar and bottom sheet accordingly.
15.2 Native iPhone (later, post-v1)
- SwiftUI uses native gestures (
onLongPressGesture,DragGesture,magnificationGesturereserved for image zoom only).
- Text selection wrapper calls the Laravel AI endpoint (
POST /api/v1/ai/transform[/stream]).
- API remains Laravel-only. No OpenAI / Anthropic key in the app.
- Use Keychain for the Sanctum token.
- Use SSE (
URLSessiondataTaskPublisher+ line-buffered parse) for streaming AI results.
- Respect dynamic type and Reduce Motion at OS level.
16 · Backend events and API
All endpoints already exist in SPEC-STV-03. Mobile uses the same surfaces — no mobile-only endpoints.
| Method | Path (canonical, v1) | Mobile use |
|---|---|---|
| POST | /api/v1/ai/transform and /ai/transform/stream (SSE) | AI selection actions (Improve / Rewrite / Summarize / …). |
| PATCH | /api/v1/ai/transformations/{id} (post-v1.1) | Mark draft applied / discarded after navigate-away resume. |
| GET | /api/v1/pages/{uuid} | Open a page on mobile. |
| PATCH | /api/v1/pages/{uuid} | Title / icon / cover / visibility edits. |
| POST | /api/v1/pages/{uuid}/blocks | Insert from slash menu / +. |
| PATCH | /api/v1/blocks/{id} | Autosave deltas. |
| DELETE | /api/v1/blocks/{id} | Block delete (swipe-left or action sheet). |
| POST | /api/v1/comments | Comment on block / selection. |
| POST | /api/v1/pages/{uuid}/share-link | Create public share from quick page actions. |
| DELETE | /api/v1/share-links/{token} | Revoke. |
Mobile contract requirements (all already binding from SPEC-STV-03):
- Policies enforced server-side. Mobile never decides authorization.
- Workspace access validated on every request via
EnsureWorkspaceMembermiddleware.
- Mobile-friendly JSON: ISO timestamps, signed URLs for files, cursor pagination on hot lists.
- Clean error envelope (
{ error: { code, message, details } }).
- Never expose secrets, AI keys, raw S3 keys, internal IDs in URLs.
17 · Database impact
No new tables are introduced for gestures. The mobile flow reuses:
pages,page_blocks— edit surface.
comments— block / selection comments.
ai_text_transformations— every AI selection run.
activity_logs— audit (block delete via swipe, public share created from quick actions, etc.).
share_links— public share creation.
rag_chunks— read-side only viaRagService::query().
17.1 Per-user gesture preferences
Stored in settings (SPEC-STV-02 §3.23) with keys under the gesture. namespace, scoped per user when applicable:
gesture.swipe_between_pages(bool, defaultfalse)
gesture.haptics_level(off | subtle | full, defaultsubtle)
gesture.long_press_ms(number, default350)
gesture.swipe_left_action(comment | duplicate | delete, defaultdelete)
gesture.swipe_right_action(move | convert | ai, defaultai)
gesture.dev_mode(bool, defaultfalse)
Workspace-level overrides under workspace_settings.gesture.* (admin only) take precedence when set.
18 · Testing requirements
18.1 Backend tests (Pest feature)
- Mobile AI transform endpoint: happy path returns streamed result + final.
- AI replace requires explicit apply: a
transformcall alone does not changepage_blocks.content.
- Discarded AI result does not change content:
applied = falserow exists; block unchanged.
- Selected text result is logged in
ai_text_transformationswithaction,tokens_in,tokens_out.
- Public pages cannot edit:
/p/{token}rejects every mutating request.
- Private page data does not leak through public pages (comments, member list, related pages all stripped).
- Block reorder via mobile move endpoint saves the correct fractional position.
- Permissions: viewer cannot edit; commenter cannot create blocks; IDOR across workspaces refused.
- Text-selection toolbar must be disabled for password / token input fields server-side: any AI transform whose target is a password field returns
validation.field_blocked.
- Mobile public page renders without editor JS: HTML smoke test asserts
<script src=".../editor."is absent on/p/{token}.
- RAG ON / OFF respected: with
rag.enabled = false,use_kb: truerequests are downgraded; response carrieskb_used: false.
18.2 Browser / UI tests (Playwright on mobile emulation; iPhone 14 / Pixel 7)
- Select text → Improve → preview → Replace.
- Select text → Summarize → Discard → original text unchanged.
- Long press block → action menu visible → Duplicate works.
- Swipe from left edge → drawer opens; swipe back → drawer closes.
- Mobile slash command menu opens on
/and on+.
- Two-finger tap undoes the last edit; three-finger tap shows the undo/redo menu.
- Bottom sheet drag handle moves through Compact → Half → Full; second swipe-down asks for discard confirmation.
- Floating toolbar repositions above the keyboard when the keyboard opens mid-selection.
18.3 Accessibility audit
- VoiceOver / TalkBack reads block role + content correctly.
- All gestures have a visible button alternative.
- Tap targets ≥ 44×44 px (Axe rule + manual scan).
prefers-reduced-motiondisables non-essential transitions / haptics.
19 · Acceptance criteria
This spec is complete when:
docs/MOBILE_GESTURES_AND_TEXT_SELECTION_EDITOR.mdexists in the repo, mirroring this page.
- Mobile gestures are fully documented (§3, §4).
- Text selection UX is fully documented (§5).
- AI Improve / Rewrite / Summarize mobile flow is documented (§6).
- Bottom sheet AI preview is documented (§7).
- Mobile slash command menu is documented (§8).
- Public mobile page behavior is documented (§11).
- Gesture conflict rules exist (§14).
- Accessibility alternatives exist (§13).
- Backend / API requirements are listed (§16).
- Tests are listed (§18).
- This file is linked from:
docs/MOBILE_UX.md(SPEC-STV-10)
docs/AI_ASSISTANT.md(SPEC-STV-08)
docs/EDITOR_SYSTEM.md(SPEC-STV-04)
.cursor/rules/mobile.mdc(SPEC-STV-12 §6.2)
- SPEC-STV-HUB Spec Registry (§10) has a row for SPEC-STV-13.