> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sesameterminal.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Interactions

# Interactions

How input maps to actions everywhere in the app. Component looks are in
[components.md](components.md); screen layouts are in [screens/](screens/).

## Preconditions for trading

A trade action (anything that places or moves one of your orders) needs:

1. Trading enabled on the server (`/status` `trading_enabled`: `LIVE_TRADING` set and not
   geoblocked).
2. The Safe/Armed switch on Armed (server state).
3. Fresh data for the price being clicked (not stale).

The UI reflects these states so the user knows what a click will do. `engine/risk`
enforces them; the UI hiding a button is not a safety control. **Cancel actions only
need a connection:** Cancel all, cancel-market and single cancels work in Safe, with
trading off, and on stale data.

## Click, drag and long-press

### Event timing

* **Mouse:** a trade fires on `pointerdown` (primary button). It does not wait for
  `click`, and no animation delays it.
* **Touch and pen:** a trade fires on tap: pointer up within 500 ms and less than 8 px
  of movement. More movement is a scroll; a longer press is a long-press. This keeps
  scrolling from placing orders.
* **Double-click guard:** a second activation of the same target (same market, side,
  price and stake) within 350 ms reuses the first click's idempotency key, so the
  backend places one order. A click after 350 ms is a new order. (The 350 ms is a
  behaviour constant in the frontend trade helper, not a motion token.)

### The one rule for right-click and long-press

**Right-click (desktop) or long-press (touch, 500 ms) on your own order cancels it. On
anything else it opens that thing's context menu** (desktop menu, mobile action sheet).
Long-press gives haptic feedback where supported (`navigator.vibrate(10)`). The browser
context menu is suppressed only inside trading panels.

### Ladder (desktop)

| Input | Where | Action |
| - | - | - |
| Click | Bid cell at any level | Buy the active stake at that price (GTC). At or above the best ask it fills against the book |
| Click | Ask cell at any level | Sell the active stake's worth at that price, capped at your position. Disabled with no position |
| Shift+click | Ask cell | Sell your **whole position** at that price |
| Click | The `[bell]` revealed in the P\&L cell of the hovered level | Set a price alert at that level, prefilled ([alerts.md](screens/alerts.md#from-a-ladder-level-one-click)). Works in Safe |
| Alt+click | A Bid or Ask cell | Same as the bell |
| Right-click | A level with your orders | Cancel your orders at that level on that side (price cell: both sides) |
| Right-click | A level with no orders | Context menu: `Set alert at 42¢`, `Sell all at 42¢`, `Centre` |
| Drag | Your order chip | Reprice: see [drag to reprice](#drag-to-reprice) |
| Click | Your order chip (no movement) | Select the order (for `[` `]` `Delete`). Pointerdown on your own chip never places an order |
| Wheel | Anywhere | Native scroll. Never changes a price or stake |

### Ladder (mobile bottom sheet)

| Input | Where | Action |
| - | - | - |
| Tap | Bid / ask cell with none of your orders on that side | Buy / sell the active stake at that price |
| Tap | A cell holding your order on that side | Select that order. The order bar appears above the stake bar: `[−] 42¢ [+]  [Cancel]`. Tap elsewhere to deselect. (To add a second order at the same level, use `+ Add here` in the order bar) |
| Long-press | A cell holding your orders | Cancel them |
| Long-press | Any other cell | Action sheet: `Set alert at 42¢`, `Sell all at 42¢` |
| Vertical swipe | Ladder body | Native scroll |
| Swipe down | Sheet handle or header | Dismiss the sheet |

### Drag to reprice

Desktop only (mobile uses the order bar steppers).

1. Pointerdown on a placed or partially filled chip, then move more than 4 px: drag
   starts. The original chip turns dashed and stays at its level.
2. A ghost chip follows the pointer snapped to rows; the target row gets a dashed
   `--ring` outline; the cursor tag reads `Move buy 25 → 44¢`. If the target crosses the
   book (a buy at or above the best ask), the tag adds `· fills now`.
3. Release on a row: send one reprice (the backend cancels and re-places; Polymarket has
   no amend). Chips show the `moving` state until both steps resolve.
4. Release outside the ladder, on the original row, or press Esc: no action.
5. The ladder does not auto-scroll during a drag. Near the edge, the user scrolls with
   the wheel while holding the chip.

Delayed, sending and cancelling chips cannot be dragged.

### Board, watchlist, portfolio, orders

| Input | Desktop | Mobile |
| - | - | - |
| Buy/Sell price button | Click: order at the shown price, active stake | Tap: same |
| Row or card body (not a button) | Board/watchlist: dock this outcome's ladder. Portfolio: expand the inline ladder. Orders: select the row | Board/watchlist/portfolio: open the ladder sheet. Orders: nothing (use the buttons) |
| Right-click / long-press on an order row | Cancel that order | Cancel that order |
| Right-click / long-press on another row | Context menu: `Open market`, `Dock ladder`, `Pin`, `Set alert…` (board: the three game alert items instead), `Cancel orders in this market (N)` | Action sheet, same items |
| Market name link | Open market view | Open market view |

### Deferred list changes

Principle 3 applied to lists. Row **values** update live. Row **structure** (insert,
remove, reorder) is deferred while the list is in use:

* In use = the pointer is over the list (desktop), or a finger touched it in the last
  1.5 s (mobile).
* A row that should be removed stays in place as a ghost: stale-styled, struck through,
  not interactive.
* New rows and re-sorted rows are held. A 24 px pill at the top of the list reads
  `3 updates`. Clicking it applies them.
* When the list stops being in use, pending changes apply in one frame, with no layout
  animation.
* Rows inserted above the viewport never shift visible rows: lists use scroll anchoring
  (`overflow-anchor: auto`) with stable row keys.

## Stake selection

* The active stake is a pUSD amount picked from the presets in settings (1–6 values).
  It is global for the session and survives route changes; it resets to the first preset
  on reload.
* Where it shows: the top-bar segmented control (desktop), the stake bar (mobile), the
  ladder footer, and every cursor tag.
* How to change it: click a segment, or in a focused trading panel (armed) press `1`–`6` to
  pick a preset, or `-` / `=` to step to the previous / next one (stopping at the ends).
* How it becomes an order:
  * **Buy** at price p: size = stake ÷ p shares.
  * **Sell** from a Sell button or ask cell: size = min(stake ÷ p, position).
  * **Exit** (portfolio, dock and the ladder header), **Shift+click sell**: size = whole position.
  * **Hedge**: size comes from the backend's hedge calculation.
  * In every case the backend rounds to the market's size tick and minimum size in
    `engine/risk`. If the result is below the minimum, the order is rejected with the
    minimum stated.
* The cursor tag and the mobile stake bar show the estimated size before the click
  (`$25 → 59.52 sh`).

## Hotkey map

Hotkeys register only through `lib/hotkeys`. There are three classes:

1. **Navigation:** always on, in Safe or Armed. They never touch orders.
2. **Global safety:** Cancel all. Works everywhere, including inside text inputs, in Safe,
   and with any panel focused.
3. **Trading:** only in the focused panel: the last one clicked, or reached with `F6`.
   Ignored while focus is in a text input. Keys that place, move or cancel orders work only
   when Armed. Keys that only move a cursor or a selection, or open something (the arrows,
   `Space`, `Enter` where it opens a ladder or a market, `Alt+Enter`, `Esc`), send nothing
   and also work in Safe; they are marked "(Safe too)" below.

**Enter and Space belong to a focused control first.** While a button, link, tab or field
inside the panel has keyboard focus, `Enter` and `Space` activate that control as usual.
Moving a panel's cursor with the arrows moves keyboard focus from the control to the panel,
so the next `Enter` or `Space` acts on the cursor.

**Focused panel indicator.** The focused panel shows a 2 px `--ring` line along its top edge,
inside the panel, so nothing moves. It shows only when it tells the user something:

* after `F6` / `Shift+F6` (and `Q`, which moves focus to the slip), in Safe or Armed, so
  keyboard users see where focus went; the next click hides it again unless the rule below
  holds;
* while Armed, when more than one visible panel could take the trading keys: two or more
  panels with trading keys (the board beside a docked ladder or the slip), or a focused
  panel without them (the collapsed bottom dock, or its Fills tab) beside one that has them.

With one panel on screen, or in Safe, a click shows nothing; the trading keys still act on
the last-clicked panel. The line keeps at least 3:1 against both surfaces in both themes.
Controls keep their own `:focus-visible` ring.

`Mod` = Ctrl on Windows/Linux, ⌘ on macOS. Single-letter keys have no modifier.

### Navigation (always on)

| Keys | Action |
| - | - |
| `Mod+K`, `/` | Open the command palette |
| `?` | Hotkey cheat sheet (a dialog listing this page) |
| `Esc` | Close the top overlay; else cancel a drag; else clear the selection |
| `g` then `l` / `w` / `p` / `o` / `a` / `n` / `s` | Go to Live / Watchlist / Portfolio / Orders / Alerts / Notifications / Settings |
| `F6` / `Shift+F6` | Focus the next / previous panel |
| `` ` `` | Show or hide the bottom dock |
| `Alt+Enter` in a focused ladder | Set a price alert at the keyboard-cursor level (alerts place no orders, so this works in Safe) |
| `P`, `Delete` in the alerts list | Pause or resume / delete the alert under the cursor ([alerts.md](screens/alerts.md#keys-navigation-class-work-in-safe)) |
| `L` | Toggle combo mode ([combos.md](screens/combos.md#hotkeys)) |
| `Space` on a board, watchlist or market-view outcome line, combo mode on | Use the outcome as a leg, or remove it |
| `Q` while the slip holds legs | Get quote, and focus the slip |
| `Esc`, `↑` / `↓`, `Delete` in the focused slip | Decline the live quote; move the leg cursor; remove that leg |
| `E` in Portfolio › Combos | Exit the selected combo: request its sell quote |

### Global safety

| Keys | Action |
| - | - |
| `Mod+Shift+X` | **Cancel all**: every open order on every venue |

### Trading (Armed, focused panel)

Any trading panel:

| Keys | Action |
| - | - |
| `1`–`6` | Pick stake preset 1–6 |
| `-` / `=` | Pick the previous / next stake preset (stops at the first and last) |

Ladder (docked, in market view, or the mobile sheet with a hardware keyboard):

| Keys | Action |
| - | - |
| `B` | Buy the active stake at the best ask |
| `S` | Sell the active stake's worth at the best bid |
| `Shift+B` | Buy the active stake at the best bid (join the bid) |
| `Shift+S` | Sell the active stake's worth at the best ask (join the offer) |
| `F` | Exit: sell the whole position at the walk price (portfolio.md, "Values"), as the header's `Exit` button |
| `H` | Hedge |
| `C` | Cancel all your orders in this market |
| `[` / `]` | Move the selected order (or your most recent one) one tick down / up |
| `Delete`, `Backspace` | Cancel the selected order |
| `Enter` | Act on the cell under the keyboard cursor, as a click would: a Bid cell buys the stake, an Ask cell sells the stake's worth, at that level's price |
| `↑` / `↓`, `←` / `→` | Move the keyboard cursor between levels, and between the Bid and Ask cells. The first press places it at the touch: `↑` / `→` on the best ask's Ask cell, `↓` / `←` on the best bid's Bid cell. The cursor cell has a 2 px `--ring` outline inside it, and the ladder scrolls just enough to keep it in view (Safe too) |
| `Alt+Enter` | Set a price alert at the cursor's level, as the level's bell does (Safe too) |
| `Space` | Re-centre the ladder on the touch (Safe too) |
| `Esc` | Clear the keyboard cursor, when no overlay is open (Safe too) |

While the keyboard cursor is on the ladder, it counts as in use: it does not re-centre by
itself until `Esc` clears the cursor (`Space` and the edge pills still re-centre). A tick
size change rebuilds the grid and clears the cursor.

Board, watchlist and the market view's outcome list (each outcome is one line):

| Keys | Action |
| - | - |
| `↑` / `↓` | Move the row cursor between outcome lines (Safe too) |
| `←` / `→` | Move the row cursor to the first line of the previous / next game (watchlist and market view: market) (Safe too) |
| `B` / `S` | Buy at the ask / sell at the bid on the outcome under the cursor |
| `Enter` | Board and watchlist: dock that outcome's ladder. Market view: show that outcome in the view's ladder (Safe too) |
| `Alt+Enter` | Dock that outcome's ladder. Market view: go to Live and dock it there, as the palette's `Alt+Enter` does (Safe too) |
| `C` | Cancel all your orders in that game's markets |

Each outcome sits on its own line with its Sell and Buy buttons, and `B` / `S` already pick
the side, so `←` / `→` have no outcomes to move between within a line; they move between
games instead. In the market view the outcome list is its own panel: click it (or `F6`) for
these keys; the rest of the view keeps the ladder's keys.

Portfolio and the dock's Positions tab:

| Keys | Action |
| - | - |
| `↑` / `↓` | Move the selection (Safe too) |
| `E` | Exit the selected position (sell all at the walk price) |
| `J` | Exit at join ask (sell all resting at the best ask) |
| `A` | Add the active stake at the best ask |
| `C` | Cancel all orders in the selected position's market |
| `Enter` | Portfolio: expand or collapse the inline ladder. Dock: open the market view (its rows have no inline ladder) (Safe too) |

Orders and the dock's Orders tab:

| Keys | Action |
| - | - |
| `↑` / `↓` | Move the cursor (Safe too) |
| `Space` | Toggle selection of the row under the cursor (Safe too) |
| `Mod+A` | Select all visible rows (Safe too) |
| `[` / `]` | Move the selected orders (else the order under the cursor) one tick down / up |
| `Delete`, `Backspace` | Cancel the selected orders (else the order under the cursor) |

In the dock, the keys follow the open tab; the collapsed dock and the Fills tab have none.
The cursor and selection last while the tab stays open.

Combo slip (focused):

| Keys | Action |
| - | - |
| `Mod+Enter` | Accept the live quote (not in its first 500 ms, nor under 1 s left) |
| `1`–`6` | Pick stake preset 1–6 |

While combo mode is on, `B` and `S` do nothing on the board, watchlist and market-view
outcome list, because their price buttons are leg toggles. The ladder keeps its keys.

When a trading hotkey is pressed in Safe, nothing is sent and the Safe/Armed switch
shows its one-off ring highlight. Cancel hotkeys (`C`, `Delete`) are in the trading class
too; in Safe, the Cancel buttons and right-click still cancel.

## Sports delay window

On in-play sports markets the venue holds a marketable order for a few seconds before
matching it (status `delayed`), and it cannot be cancelled during that time.

* **Before the click:** a market with an in-play delay shows a delay chip in the ladder
  header and on the board row: `3s delay` (muted text, no icon). Users know a crossing order
  will wait.
* **On the click:** the order chip goes `sending`, then `delayed` with a countdown ring
  and seconds, at its price level. The board row's pending-order dot shows the warning
  colour.
* **During the delay:**
  * Right-click, long-press, drag, `[`/`]`, stepper and Delete on that order do nothing.
    The tooltip or toast reads `In delay window (2s)`.
  * Position, P\&L and liquidation value do not change: they change on fills only.
  * Other orders in the market can still be placed and cancelled.
* **Cancel all or cancel-market during a delay:** every other order is cancelled; each
  delayed order gets a queued cancel (`cancel_pending`), sent when its window ends, and the
  result toast says so: `Cancelled 5 · 2 in delay window`.
* **When the delay ends:** the chip moves to `filled`, `partially filled` (the rest
  rests as `placed`), or `placed`, as reported by the user feed.
* If the backend does not send an end time, the chip reads `Delayed` without a ring.

## Pre-game wipe

The venue cancels resting orders on sports markets when the game starts.

* **Before the start**, while you have resting orders in that game: the ladder status line
  and the board row show `Starts in 8:04 · resting orders cancel at start` (warning
  fill in the last 10 minutes, muted before). On the board it is the `Wipe` chip, with that
  text in its tooltip; a phone card, which has no tooltip, says `Orders cancel at start`.
* **One start format.** Every view writes a start the same way, from the same clock: `in 1:41`
  (m:ss) in the last 10 minutes, `in 12m` inside the hour (minutes rounded down from the
  seconds left, so `in 10m` is followed by `in 10:00`), `in 3h` under 24 hours (lib/format's
  `formatWhen`), then `3 Oct 14:47`, with the exact local time in the tooltip. The board clock, score badges, the ladder strip, the market's Rules tab and
  the palette all show it, and tick every second in the last 10 minutes.
* **At the start**, the `game_start_orders` notification: `Orders cancelled at start` /
  `3 orders · Lakers v Celtics` ([notifications.md](screens/notifications.md)), sent once
  every order open at the start has ended, at most 30 s after it. It lists only orders the
  venue reported cancelled without a cancel from this app, each for its unmatched size. Its toast
  carries `Open`; `Re-post` lives on the ladder status line and the board row, which read
  the wiped orders from the notification's `orders`. The ladder status line shows
  `3 orders cancelled at start · Re-post` for 5 minutes or until dismissed; an order the
  venue left open is not offered.
* **Re-post** is one click and needs Armed. It sends the same side, price and unmatched size
  for each wiped order as new orders, each through risk; each rejected one gets its own
  reject toast (orders now outside the price band, for example). Re-posted and dismissed
  orders are not offered again on any device: the server keeps each order's `handled_at`,
  set by the placement with the order's `client_order_id` or by Dismiss, and sends the change
  to every session. That `client_order_id` is minted by the server with the list, so a second
  Re-post from any tab or device returns the first placement instead of a second order.

## Error and reject feedback

Messages are factual: what happened and the numbers, no advice (CLAUDE.md).

| Source | Where it shows | Example text |
| - | - | - |
| Risk reject (`engine/risk`) | Rejected chip at the level for 3 s + the click's reject toast (8 s), titled with the reason and naming the order (`Lakers · Buy 42¢ · $25.00`); the `order_rejected` notification carries the same reason (copy in notifications.md). The examples here are the reason part. A limit reject shows the limit and the order's figure that `Reject` carries (`limit`, `value`, `reference_price`), USD with two decimals and shares without trailing zeros (lib/format); without them it shows the fixed text for its code | `Rejected · max order $100.00 (order $250.00)` |
| | | `Rejected · max position $1,000.00 (position $1,234.50)` |
| | | `Rejected · max daily $1,000,000.00 (today $1,000,250.00)` |
| | | `Rejected · max open orders 100` |
| | | `Rejected · max orders per minute 60` |
| | | `Rejected · price outside band (±5 ticks of 42¢)`; one tick reads `±1 tick` |
| | | `Rejected · below minimum size (5 sh)` |
| | | Combos: `Rejected · max combo $5,000.00 (combo $6,000.00)`, `Rejected · max combo exposure $25,000.00 (exposure $25,100.00)`, `Rejected · max combo legs 10 (12 legs)`, `Rejected · max quotes per minute 12, retry after 5s` |
| | | `Rejected · Safe` / `Rejected · trading off` |
| Venue reject | Same | `Rejected · post-only mode` |
| 425 engine restart | Toast | `Not placed · venue restarting (425)` |
| 429 rate limit | Toast | `Not placed · rate limited (429), retry after 2s` |
| 503 cancel-only | Toast + status pill `Cancel-only` (warning) until it clears | `Not placed · venue in cancel-only mode (503)` |
| Timeout or unknown outcome | The chip shows `[circle-help]` with a dashed border: `Checking…` until the backend reconciles, then becomes `placed` or disappears with toast `Not placed`. The toast stays until the outcome is known; after 30 s, when the backend's fast lookups end (ADR 0011), it reads `Checking order · no response after 30s` with `· looked up every 60s, never re-sent` after the order, for 8 s, and the status pill keeps counting the order | `Checking order · no response` |
| Cancel failed | Chip returns to `placed`; toast | `Cancel failed · 503 cancel-only mode` |
| Browser lost the backend | Whole-app stale, status pill `Disconnected`, trade clicks disabled | none |
| Form validation | Under the field, `--destructive` text, body-sm | `Must be at least 1` |

Rules:

* **Never auto-retry** an order or reprice from the UI. After a failure, the user clicks
  again. The UI never resends on its own, and neither does the backend (CLAUDE.md).
* The reject toast always names the order (side, outcome, price), because the user may
  have clicked several.
* Rejected and failed intents are listed in the Orders screen's `Rejected` tab for the
  session.

## Click budgets

PLAN §4.1 budgets, with the design path that meets each. "Armed" is a precondition
(one click per session on the switch) and is not counted.

| Task | Budget | Desktop path | Clicks | Mobile path | Taps |
| - | - | - | - | - | - |
| Buy from the board or watchlist | 1 click | Click the green Buy price on the outcome row (stake already selected in the top bar) | **1** | Tap the Buy price button on the game card | **1** |
| Open any market | Ctrl+K + typing + Enter | `Mod+K`, type, `Enter` (top result ranked pinned > held > live > volume) | **keys only** | Search tab, type, tap the result | **2 taps + typing** |
| Exit a position | 1 click | Portfolio or dock row `Exit 39¢`; or ladder header `Exit 39¢`; or `F` / `E` | **1** | Portfolio card `Exit 39¢` button | **1** |
| Cancel all orders on a market | 1 click | Ladder header `Cancel (3)`; portfolio row `×3`; orders group header `Cancel (3)`; or `C` | **1** | Ladder sheet header `Cancel (3)`; portfolio card `×3` | **1** |
| Move an order | 1 drag, or 1 click per tick | Drag the chip on the ladder (**1 drag**); or the `−`/`+` stepper in the orders table or dock (**1 per tick**); or `[`/`]` | **1 / tick** | Orders tab card stepper (**1 per tick**). In the ladder sheet: tap the chip, then 1 per tick (**1 + ticks**) | **1 / tick** |
| Kill everything | 1 click or 1 hotkey | Top bar `Cancel all`, or `Mod+Shift+X` | **1** | Header `[x] 7` (visible above every screen and sheet) | **1** |

Combo budgets (build and buy in 6, exit in 2) are in
[combos.md](screens/combos.md#click-budgets).

Mobile ladder-sheet repricing costs one extra tap because a tap on a price level has to
place orders. The Orders tab meets the budget exactly (see
[open question 13](index.md#open-questions-for-the-tech-lead)).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.