Docs accuracy fixes from #110: slug 400s, fetch limit 100, visibility field, PALETTE_DB, e2e design table/sections/links

fen
2026-09-09 19:15:44 -05:00
parent 44da8699d5
commit 0440d32742
4 changed files with 17 additions and 19 deletions
+3 -3
@@ -38,9 +38,9 @@ machine-readable `code` the web UI maps to plain-language guidance (#105):
| Code | Status | Meaning | | Code | Status | Meaning |
|---|---|---| |---|---|---|
| `content_empty` | 400 | content is required | | `content_empty` | 400 | content is required |
| `slug_invalid` | 400/409 | custom slug malformed | | `slug_invalid` | 400 | custom slug malformed |
| `slug_taken` | 409 | custom slug already in use | | `slug_taken` | 400 | custom slug already in use |
| `slug_reserved` | 409 | custom slug is reserved | | `slug_reserved` | 400 | custom slug is reserved |
| `expiry_invalid` | 400 | expires_in out of 1 minute 1 year range | | `expiry_invalid` | 400 | expires_in out of 1 minute 1 year range |
| `content_too_large` | 413 | content or body exceeds the size cap | | `content_too_large` | 413 | content or body exceeds the size cap |
| `rate_limited` | 429 | too many requests; see `Retry-After` | | `rate_limited` | 429 | too many requests; see `Retry-After` |
+1 -1
@@ -66,7 +66,7 @@ Specifically:
- DB file > ~5-10 GB, or - DB file > ~5-10 GB, or
- a concrete user request for a Postgres-backed image. - a concrete user request for a Postgres-backed image.
4. When a trigger fires, port `internal/store` to Postgres behind an interface extracted *then* — the refactor is mechanical against a real need, instead of speculative complexity now. 4. When a trigger fires, port `internal/store` to Postgres behind an interface extracted *then* — the refactor is mechanical against a real need, instead of speculative complexity now.
5. For the docker image: `PALETTE_DB_PATH` env already implies the deployment choice; no extra backend knob needed. 5. For the docker image: `PALETTE_DB` env already implies the deployment choice; no extra backend knob needed.
## Summary ## Summary
+6 -8
@@ -2,7 +2,7 @@
- **Issue:** #39 - **Issue:** #39
- **Status:** Design (no implementation in this PR) - **Status:** Design (no implementation in this PR)
- **Related docs:** [docs/API.md](../API.md) - **Related docs:** [the wiki `API` page](https://git.archfox.org/poslop/palette/wiki/API)
## 1. Goals and non-goals ## 1. Goals and non-goals
@@ -94,7 +94,7 @@ The full envelope can also just live inside `content` (server-opaque); the `encr
### 3.2 Create response ### 3.2 Create response
Unchanged shape: `id`, `url`, `raw_url`, `api_url`, and the one-time `deletion_token` documented in docs/API.md. Unchanged shape: `id`, `url`, `raw_url`, `api_url`, and the one-time `deletion_token` documented in the wiki `API` page.
### 3.3 Get response ### 3.3 Get response
@@ -149,11 +149,9 @@ Delete, redeem, rate limits, expiry, sweeper, deletion tokens, visibility, slugs
- **Existing /api/mine, /api/public list shapes gain a flag** (additive, non-breaking). - **Existing /api/mine, /api/public list shapes gain a flag** (additive, non-breaking).
- **Copy-to-clipboard of decrypted text stays client-side**, fine. "Copy raw" on an encrypted paste copies the envelope — label it clearly. - **Copy-to-clipboard of decrypted text stays client-side**, fine. "Copy raw" on an encrypted paste copies the envelope — label it clearly.
## 5. UX for key sharing ## 6. UX for key sharing
### 5.1 Three sharing modes ### 6.1 Three sharing modes
| Mode | What's shared | Security level | Use case |
|---|---| malformed JSON / wrong key | | |
| Mode | What's shared | Strength | Use case | | Mode | What's shared | Strength | Use case |
|---|---|---|---| |---|---|---|---|
| **Passphrase** (default) | URL + passphrase out-of-band (Signal etc.) | Good — two channels | Team snippets, sensitive configs | | **Passphrase** (default) | URL + passphrase out-of-band (Signal etc.) | Good — two channels | Team snippets, sensitive configs |
@@ -162,13 +160,13 @@ Delete, redeem, rate limits, expiry, sweeper, deletion tokens, visibility, slugs
Browsers never transmit `#` fragments to servers; still set `Referrer-Policy: no-referrer` site-wide and offer separate copy buttons for URL and key. Key-in-fragment ships with a warning and stays opt-in. Browsers never transmit `#` fragments to servers; still set `Referrer-Policy: no-referrer` site-wide and offer separate copy buttons for URL and key. Key-in-fragment ships with a warning and stays opt-in.
### 5.2 Create page (`/new`) UX ### 6.2 Create page (`/new`) UX
- "Encrypt content" toggle → reveals passphrase field + strength meter + generate-random-key button. - "Encrypt content" toggle → reveals passphrase field + strength meter + generate-random-key button.
- When encrypting, hide the server-side language dropdown; the client detects language after decryption. - When encrypting, hide the server-side language dropdown; the client detects language after decryption.
- Two separate inputs with distinct labels: "Access password (checked by the server, 401 gate)" and "Encryption passphrase (never leaves your browser)". If both hold the same value, warn. - Two separate inputs with distinct labels: "Access password (checked by the server, 401 gate)" and "Encryption passphrase (never leaves your browser)". If both hold the same value, warn.
### 6.2 View page (`/{id}`) UX ### 6.3 View page (`/{id}`) UX
- If `encryption.kdf` is present → show key entry UI (after the access-password 401 gate, if that also applies). - If `encryption.kdf` is present → show key entry UI (after the access-password 401 gate, if that also applies).
- After decrypt: normal render pipeline, language detected client-side. - After decrypt: normal render pipeline, language detected client-side.
+7 -7
@@ -1,26 +1,26 @@
# Performance notes (#32) # Performance notes (#32)
Background: history and saved pages filter client-side. Each load fetches the Background: history and saved pages filter client-side. Each load fetches the
most recent rows from the list endpoint (`limit=500` per query is the current most recent rows from the list endpoint (`limit=100` per query is the current
client cap in `static/table.js`) and filters/sorts in the browser. This note client cap in `static/table.js`) and filters/sorts in the browser. This note
records current behavior, measured latency, and the design for a future records current behavior, measured latency, and the design for a future
server-side search endpoint. Measurements only — no implementation in #4/#32. server-side search endpoint. Measurements only — no implementation in #4/#32.
## Current behavior ## Current behavior
- `/api/public?limit=500&offset=0` and `/api/mine?limit=500&offset=0` return up - `/api/public?limit=100&offset=0` and `/api/mine?limit=100&offset=0` return up
to 500 rows (id, title, language, created_at, view_count, size, to 100 rows (the server clamps list `limit` at 100) (id, title, language, created_at, view_count, size, visibility,
custom_slug, is_can). Content is NOT included — only `LENGTH(content)`. custom_slug, is_can). Content is NOT included — only `LENGTH(content)`.
- The browser applies the search-box filter (title/language/id substring) and - The browser applies the search-box filter (title/language/id substring) and
column sorting locally over the fetched window. column sorting locally over the fetched window.
- Consequence: search only covers the fetched window (500 most recent rows). - Consequence: search only covers the fetched window (100 most recent rows).
Older rows are invisible to search until paginated through, and each query Older rows are invisible to search until paginated through, and each query
ships ~4 KB of row metadata regardless of how few rows the user will look at. ships ~4 KB of row metadata regardless of how few rows the user will look at.
## Measured latency (synthetic rows, scratch SQLite DB) ## Measured latency (synthetic rows, scratch SQLite DB)
Rows are synthetic pastes (~200 B content each, indexed like production: Rows are synthetic pastes (~200 B content each, indexed like production:
`idx_pastes_visibility_created`). Queried `GET /api/public?limit=500` `idx_pastes_visibility_created`). Queried `GET /api/public?limit=100`
(modernc.org/sqlite, WAL, single connection — same as production). (modernc.org/sqlite, WAL, single connection — same as production).
| Rows in table | Bulk insert | First query | Avg query (10 runs) | Payload | | Rows in table | Bulk insert | First query | Avg query (10 runs) | Payload |
@@ -32,8 +32,8 @@ Rows are synthetic pastes (~200 B content each, indexed like production:
Interpretation: Interpretation:
- The list query itself is cheap (< 2 ms at 10k rows); latency users perceive - The list query itself is cheap (< 2 ms at 10k rows); latency users perceive
comes from network + browser rendering of 500 rows, not SQL. comes from network + browser rendering of 100 rows, not SQL.
- The current design scales fine to ~10k pastes. Beyond that, shipping 500 - The current design scales fine to ~10k pastes. Beyond that, shipping 100
rows per keystroke-refresh cycle is wasteful and search coverage stays rows per keystroke-refresh cycle is wasteful and search coverage stays
capped at the window. capped at the window.