👆

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

  1. Mobile editing must feel fast, clean, and native.
  1. Reading must be easier than editing — viewers should not be forced into editor chrome.
  1. Editing must never feel cramped. The composer always has air.
  1. All critical actions must be reversible. Hard deletes require explicit confirm; soft deletes show an undo snackbar for 6 seconds.
  1. Accidental deletes must be prevented. No swipe gesture maps to a destructive action without confirmation.
  1. Gestures must not conflict with browser or native OS gestures (iOS back-swipe, Chrome pull-to-refresh).
  1. Text selection must be precise and reliable. Native selection handles are not hidden by app chrome.
  1. AI must never replace text without user approval. The Preview Gate is mandatory (SPEC-STV-08 §3).
  1. Public pages must be beautiful and readable on mobile.
  1. 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 Esc from 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: Tab cycles chips, Enter activates.
  • Screen-reader: each chip has aria-label; the toolbar group has role=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:

  1. User selects text (§5).
  1. Floating toolbar appears.
  1. User taps Improve / Rewrite / Summarize (or More … for the full SPEC-STV-08 action set).
  1. The AI Preview Bottom Sheet opens (§7).
  1. Result streams in real time over SSE (POST /api/v1/ai/transform/stream).
  1. User taps one of: Replace selection · Insert below · Copy · Try again · Discard.

Rules (mandatory):

  • AI never auto-applies. Apply is gated on a final final SSE 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 = true for the user.
  • Every call writes one ai_text_transformations row (SPEC-STV-08 §8). applied = true only on Replace / Insert below.
  • Respect workspace ai_settings.enabled (off → toolbar AI chips hidden).
  • Respect rag.enabled (off → the request is sent with use_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 delta event 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 final event.

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 visualViewport API.
  • 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)
  • Aa formatting (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.height to 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.share when 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.

EventHaptic
Block moved (drop)Light tap (10 ms)
AI result appliedLight tap
Page saved (autosave success)none (visual only)
Public link copiedLight tap
Comment addedLight tap
ErrorNotification pattern (short-pause-short)
Destructive action confirmWarning 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-label or aria-labelledby).
  • Every action has a keyboard equivalent when a hardware keyboard is attached (⌘/Ctrl + B/I/U, ⌘/Ctrl + K palette, Tab cycles 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: reduce disables 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

  1. Text selection has priority over page navigation. While a selection is active, edge-swipes do not open drawers.
  1. 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.
  1. 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.
  1. Scroll must remain natural. No touch-action: none on whole-page elements. Use touch-action: pan-y on horizontally-scrollable embeds (code, table).
  1. 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.
  1. Keyboard shortcuts must not conflict with mobile OS shortcuts. Avoid ⌘+Space, ⌘+H, etc.
  1. Public pages prioritize reading. Most gestures are disabled on public pages except scroll, selection, TOC drawer, share, and copy.
  1. 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 touchstart and mousedown handlers.
  • touch-action: manipulation on tappable buttons (suppresses 300 ms double-tap zoom).
  • Never set touch-action: none on text containers — it breaks native selection.
  • Bottom sheets instead of desktop modals on mobile breakpoints.
  • IntersectionObserver to virtualize blocks above 200 per page.
  • Autosave debounced (500 ms idle / 5 s max) via BlockService batch endpoint.
  • Optimistic UI only for reversible actions; destructive actions wait for server ack.
  • Editor content stored as JSON (page_blocks.content); HTML rendering goes through Sanitizer (SPEC-STV-04 §11, SPEC-STV-11 §9).
  • Avoid heavy JS on public pages (§11).
  • Use visualViewport API to track keyboard height; reposition toolbar and bottom sheet accordingly.

15.2 Native iPhone (later, post-v1)

  • SwiftUI uses native gestures (onLongPressGesture, DragGesture, magnificationGesture reserved 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 (URLSession dataTaskPublisher + 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.

MethodPath (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}/blocksInsert 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/commentsComment on block / selection.
POST/api/v1/pages/{uuid}/share-linkCreate 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 EnsureWorkspaceMember middleware.
  • 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 via RagService::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, default false)
  • gesture.haptics_level (off | subtle | full, default subtle)
  • gesture.long_press_ms (number, default 350)
  • gesture.swipe_left_action (comment | duplicate | delete, default delete)
  • gesture.swipe_right_action (move | convert | ai, default ai)
  • gesture.dev_mode (bool, default false)

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 transform call alone does not change page_blocks.content.
  • Discarded AI result does not change content: applied = false row exists; block unchanged.
  • Selected text result is logged in ai_text_transformations with action, 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: true requests are downgraded; response carries kb_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-motion disables non-essential transitions / haptics.

19 · Acceptance criteria

This spec is complete when:

  1. docs/MOBILE_GESTURES_AND_TEXT_SELECTION_EDITOR.md exists in the repo, mirroring this page.
  1. Mobile gestures are fully documented (§3, §4).
  1. Text selection UX is fully documented (§5).
  1. AI Improve / Rewrite / Summarize mobile flow is documented (§6).
  1. Bottom sheet AI preview is documented (§7).
  1. Mobile slash command menu is documented (§8).
  1. Public mobile page behavior is documented (§11).
  1. Gesture conflict rules exist (§14).
  1. Accessibility alternatives exist (§13).
  1. Backend / API requirements are listed (§16).
  1. Tests are listed (§18).
  1. 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)
  1. SPEC-STV-HUB Spec Registry (§10) has a row for SPEC-STV-13.