diff --git a/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/.openspec.yaml b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/.openspec.yaml
new file mode 100644
index 0000000..515eaae
--- /dev/null
+++ b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/.openspec.yaml
@@ -0,0 +1,2 @@
+schema: spec-driven
+created: 2026-09-11
diff --git a/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/design.md b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/design.md
new file mode 100644
index 0000000..6bee2fe
--- /dev/null
+++ b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/design.md
@@ -0,0 +1,150 @@
+## Context
+
+`AnswerInput.vue` holds a plain `` that the app auto-focuses on mount
+and that the on-screen `OnScreenKeyboard.vue` writes into (see
+`openspec/specs/typing-practice/spec.md` — "On-screen keyboard for the
+exercise's language"). On a touchscreen, focusing any `` is the
+browser's own trigger for the OS virtual keyboard; the app has never
+suppressed it because the desktop/laptop case this was built for (see "The
+exercise fits on one screen") has no virtual keyboard to suppress. There is no
+device-classification, settings, or persistence code anywhere in the frontend
+yet — this change introduces all three.
+
+## Goals / Non-Goals
+
+**Goals:**
+- Keep the OS virtual keyboard off the answer field by default on phones.
+- Leave it available by default everywhere else, since a connected physical
+ keyboard cannot be detected.
+- Give the learner a persistent, explicit override in both directions.
+- Keep the on-screen keyboard's own control reachable on a phone-sized
+ screen, and let the learner hide that board entirely.
+
+**Non-Goals:**
+- General responsive layout for small screens beyond the keyboard board
+ itself (the "fits on one screen" requirement is still laptop-scoped;
+ reflowing the exercise column or the sentence's type scale for narrow
+ viewports is separate work). The keyboard board's own overflow is
+ in scope below — it turned out to block the override control's own
+ reachability during e2e verification, not just a cosmetic gap.
+- Detecting a physical keyboard directly — no browser API exposes this, which
+ is precisely why an explicit override exists.
+- A general settings panel/infrastructure beyond what these two settings
+ need — no accounts, no server-side sync, no third setting speculatively
+ added.
+
+## Decisions
+
+### Suppress via `inputmode="none"`, not `readonly` or a hidden proxy input
+Setting the field's `inputmode` attribute to `"none"` tells the browser not to
+draw its virtual keyboard on focus, while the field stays focused, editable,
+and still receives physical key events — because `inputmode` only hints at
+which virtual keyboard to show, it does not disable input. `readonly` was
+considered and rejected: it also blocks physical keyboard input and caret
+placement, which would break the "physical keyboard also works" scenario
+already in the spec. A hidden/offscreen proxy input was considered and
+rejected as needless complexity — `v-model`, `@keyup.enter`, and the visible
+caret all keep working unchanged with `inputmode` alone.
+
+`inputmode` becomes a computed property on the input, driven by whichever of
+the two settings states below is active.
+
+### Two independent pieces of state: device classification and override
+1. **Device classification** (`phone` vs. not) is computed, not stored — a
+ small composable reads it live from `matchMedia`, so it reacts if the
+ viewport or input mode changes (e.g. rotating a phone, or a tablet
+ docking a keyboard, changes `hover`/`pointer` on some platforms).
+ Classification rule: `(max-width: 640px)` — Tailwind's own `sm` breakpoint,
+ already the project's small-screen line — **and** `(hover: none) and
+ (pointer: coarse)`, i.e. touch-primary with no mouse/trackpad. Both must
+ hold; a narrow desktop window with a mouse is not a phone.
+2. **Override** is a tri-state value — `auto` (default), `on`, `off` — held in
+ a small Pinia store and mirrored to `localStorage` under one key, read
+ once at store creation and written on every change. `auto` means "follow
+ classification"; `on`/`off` force the `inputmode` regardless of it.
+
+Keeping these separate (rather than collapsing to one persisted boolean)
+means a learner who never touches the setting keeps getting the
+device-appropriate default even if they later switch devices with the same
+browser profile — `localStorage` is per-origin-per-browser, not per-device,
+so a forced boolean would follow them somewhere it no longer applies.
+
+### Settings control placement: a header menu, not the keyboard's bottom bar
+Tried first as a pill in `OnScreenKeyboard.vue`'s existing bottom bar
+(alongside the layout switch and finger legend). Phone-width e2e
+verification (Playwright, `hasTouch`/`isMobile` emulation) caught what a
+static mockup didn't: at 390px the bar is already tight, and the pill was
+genuinely unclickable — other elements physically overlapped it. Rather than
+fight that bar for space, both settings moved to a small popover menu off a
+gear icon in the app header (`SettingsMenu.vue`), positioned
+absolutely so it costs the header row no height on any viewport (`App.vue`'s
+title row must stay short — see "The exercise fits on one screen"). This
+also gave the second setting (below) a natural home instead of a second
+crowded pill.
+
+### The on-screen keyboard scrolls sideways rather than shrinking
+`OnScreenKeyboard.vue`'s panel was `w-[var(--board)]`, a fixed width; below
+that width the whole page grew wider than the viewport instead of the panel
+adapting. Shrinking every key to fit was rejected — a key below a tappable
+size defeats the board's whole point, and there is no width where a
+13-column row shrinks to fit a phone and stays tappable. Instead: the panel
+is now `w-full max-w-[var(--board)]` (shrinks with the viewport, matching
+`AnswerInput.vue`'s column), and inside it the rows + space bar sit in an
+`overflow-x-auto` wrapper at their full, unshrunk width — so on any viewport
+narrower than `--board` the keys stay true size and the learner scrolls
+sideways to reach the rest, the way they would scroll any wide content on a
+touch device. The bottom bar (legend + layout switch) sits outside that
+scroll wrapper, so it never scrolls out of reach itself; the legend also
+gained `flex-wrap` for narrow panels.
+
+### A second, independent setting: hide the on-screen keyboard
+Once a learner is typing on a physical keyboard they already know work, the
+board is a lot of screen for a hint they are not using — a plain "hide it"
+option, not tied to the virtual-keyboard override (that one is about the
+*OS* keyboard; this one is about *this app's own* board). Held as a second
+field, `onScreenKeyboardVisible`, on the same settings store, defaulting to
+shown and persisted the same way. `SentenceView.vue` wraps
+`` in `v-if`, not `v-show`: the board holds no state worth
+preserving while hidden (its layer and scroll position reset the same way a
+new exercise already resets them), so mounting fresh on re-show costs
+nothing and is simpler than tracking what to reset by hand.
+
+### Where the logic lives
+- `src/frontend/src/lib/device.js`: the `matchMedia`-backed
+ phone-classification composable/helper, unit-testable by mocking
+ `matchMedia` the way the existing `lib/keyboard` tests mock their inputs.
+- `src/frontend/src/stores/settings.js`: the Pinia store holding both
+ settings, their `localStorage` keys, and a computed `virtualKeyboardEnabled`
+ that combines the override + classification into the boolean
+ `AnswerInput.vue` needs.
+- `AnswerInput.vue`: binds `:inputmode` to `store.virtualKeyboardEnabled ?
+ 'text' : 'none'`.
+- `SettingsMenu.vue` (new): the header's gear button and its popover, reading
+ and writing both settings.
+- `OnScreenKeyboard.vue`: the `overflow-x-auto` scroll wrapper; no longer
+ holds any settings-control markup itself.
+- `SentenceView.vue`: `v-if`s the board on `onScreenKeyboardVisible`.
+
+## Risks / Trade-offs
+
+- **Classification is a heuristic and will misjudge some devices** (e.g. a
+ large phone above 640px, or a small tablet at 640px in portrait) → this is
+ exactly what the override exists for; the proposal treats the override as a
+ first-class requirement rather than an escape hatch, and the design keeps
+ the default in the "safer to leave the keyboard available" direction for
+ anything not confidently phone-sized.
+- **`inputmode="none"` support varies slightly across mobile browsers**
+ (notably some older WebKit versions honored it inconsistently) → the
+ override lets an affected learner switch it back on if suppression fails to
+ behave, and lets one switch it off if a browser shows the virtual keyboard
+ despite `inputmode="none"`; no feature detection is added since there is
+ nothing reliable to detect.
+- **`localStorage` can be unavailable** (private browsing in some browsers,
+ storage disabled) → reads/writes are wrapped so a failure falls back to the
+ in-memory default (`auto`) for that session rather than throwing.
+
+## Migration Plan
+
+Additive, frontend-only change with no persisted server data and no schema.
+Ships as a normal frontend deploy; nothing to migrate or roll back beyond
+reverting the commit if the heuristic proves wrong in practice.
diff --git a/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/proposal.md b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/proposal.md
new file mode 100644
index 0000000..18469c8
--- /dev/null
+++ b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/proposal.md
@@ -0,0 +1,60 @@
+## Why
+
+On a phone, focusing the answer field pops up the OS's virtual keyboard on top
+of the app's own on-screen Kedmanee keyboard — two keyboards fighting for the
+same screen, with the OS one usually covering the on-screen one entirely. A
+phone has no physical keyboard, so the virtual keyboard adds nothing the
+on-screen board doesn't already provide, and should stay down. A tablet is a
+harder case: some are typed on with a connected physical keyboard, but many
+are not, and the app cannot reliably detect a connected one — so it must not
+assume a tablet is keyboard-less the way a phone can be assumed to be. Because
+that assumption can be wrong in both directions, the learner needs a manual
+override.
+
+## What Changes
+
+- Suppress the OS virtual keyboard on the answer field when the device is
+ classified as a phone (small viewport, touch-primary), since it has no
+ physical keyboard and the on-screen board already covers typing.
+- Leave the virtual keyboard available by default on tablets and larger
+ touch devices, since a physical keyboard cannot be reliably detected and
+ many tablets are typed on without one.
+- Add a settings menu (a gear icon in the header) where the learner can force
+ the virtual keyboard on or off, overriding the device-based default — for a
+ tablet with a physical keyboard attached (force off) or a phone-sized
+ device misclassified as a tablet, or vice versa (force either way). The
+ choice persists across visits.
+- Fix the on-screen keyboard so it no longer forces the page wider than a
+ phone screen: it scrolls sideways below its full width instead of shrinking
+ keys past a tappable size. This surfaced during verification — the settings
+ control itself was unreachable on a real phone width until this was fixed.
+- Add a second, independent setting in the same menu letting the learner hide
+ the app's own on-screen keyboard entirely, for anyone typing on a physical
+ keyboard they already know.
+
+## Capabilities
+
+### New Capabilities
+
+(none)
+
+### Modified Capabilities
+
+- `typing-practice`: adds device-aware control over whether the OS virtual
+ keyboard is allowed to appear when the answer field is focused, a
+ learner-facing setting that overrides the device default, a requirement
+ that the on-screen keyboard stays usable (not shrunk below a tappable size)
+ on any viewport, and a setting to hide the on-screen keyboard entirely.
+
+## Impact
+
+- Affected code: `src/frontend/src/components/AnswerInput.vue` (the answer
+ field's `inputmode`), a new `SettingsMenu.vue` component (header gear
+ button + popover) replacing the answer field's earlier in-keyboard control,
+ `OnScreenKeyboard.vue` (the board's panel now scrolls instead of a fixed
+ width) and `SentenceView.vue` (conditionally renders the board), a settings
+ Pinia store backed by `localStorage`, and a small device-classification
+ helper under `src/frontend/src/lib/`.
+- No backend, API, or data changes.
+- No new dependencies expected — classification uses standard `matchMedia`
+ queries already available in the target browsers.
diff --git a/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/specs/typing-practice/spec.md b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/specs/typing-practice/spec.md
new file mode 100644
index 0000000..9775205
--- /dev/null
+++ b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/specs/typing-practice/spec.md
@@ -0,0 +1,113 @@
+## ADDED Requirements
+
+### Requirement: Virtual keyboard suppressed on phone-classified devices
+The frontend SHALL classify the device as a phone when it has a small screen
+and is primarily touch-operated (no hover-capable pointer such as a mouse or
+trackpad), and SHALL keep the OS virtual keyboard from appearing when the
+answer field is focused on such a device, unless the learner has overridden
+this. A phone has no physical keyboard, and the app's own on-screen Kedmanee
+keyboard already covers typing, so a second, OS-drawn keyboard only competes
+for the same screen space.
+
+#### Scenario: Focusing the field on a phone
+- **WHEN** the learner taps or otherwise focuses the answer field on a
+ phone-classified device, and no override is set
+- **THEN** the OS virtual keyboard does not appear, and the on-screen keyboard
+ remains the way to type
+
+#### Scenario: A physical keyboard still works on a phone-classified device
+- **WHEN** the learner types on a physical keyboard while the answer field is
+ focused on a phone-classified device
+- **THEN** the typed characters reach the field exactly as they do on any
+ other device, because suppressing the virtual keyboard does not block
+ physical key input
+
+### Requirement: Virtual keyboard available by default on tablets and larger devices
+The frontend SHALL leave the OS virtual keyboard free to appear when the
+answer field is focused on any device not classified as a phone, unless the
+learner has overridden this. Whether such a device has a physical keyboard
+attached cannot be reliably determined from the browser, and many of these
+devices are typed on without one, so the default SHALL favor not removing a
+learner's only way to type.
+
+#### Scenario: Focusing the field on a tablet
+- **WHEN** the learner taps or otherwise focuses the answer field on a device
+ not classified as a phone, and no override is set
+- **THEN** the OS virtual keyboard is free to appear as it would on any other
+ input field
+
+### Requirement: Learner can override virtual keyboard visibility
+The frontend SHALL provide a setting the learner can use to force the OS
+virtual keyboard on or off for the answer field, overriding the device-based
+default in either direction, and SHALL persist that choice across visits.
+Device classification is a heuristic and can be wrong in both directions — a
+tablet with a physical keyboard attached, or a phone-sized device the
+heuristic misreads — so the learner needs a way to correct it.
+
+#### Scenario: Forcing the virtual keyboard off
+- **WHEN** the learner turns the setting to "off" and then focuses the answer
+ field, regardless of device classification
+- **THEN** the OS virtual keyboard does not appear
+
+#### Scenario: Forcing the virtual keyboard on
+- **WHEN** the learner turns the setting to "on" and then focuses the answer
+ field, regardless of device classification
+- **THEN** the OS virtual keyboard is free to appear
+
+#### Scenario: The override persists
+- **WHEN** the learner sets the override and later reloads the application or
+ returns in a new visit, on the same device and browser
+- **THEN** the same override is still in effect, without the learner setting
+ it again
+
+#### Scenario: No override set
+- **WHEN** the learner has never changed the setting
+- **THEN** the device-classified default governs whether the virtual keyboard
+ appears, and the setting shows that this is the current, unforced state
+
+### Requirement: The on-screen keyboard fits any viewport without shrinking below a tappable size
+The on-screen keyboard's keys SHALL NOT be sized smaller than they are today
+to fit a narrow viewport; where the layout's rows are wider than the
+viewport, the frontend SHALL let the learner scroll the keyboard sideways to
+reach the rest of it instead. A key shrunk to fit a phone stops being a
+target a thumb can reliably hit, and stops being the size the finger-position
+promise this keyboard makes is about.
+
+#### Scenario: The page does not grow wider than the viewport
+- **WHEN** the exercise is displayed on a viewport narrower than the
+ keyboard's full width
+- **THEN** the page itself does not scroll or grow wider than the viewport —
+ only the keyboard's own rows do
+
+#### Scenario: Every key stays reachable
+- **WHEN** the keyboard is wider than the viewport showing it
+- **THEN** every key the current layout can type, including ones initially
+ off-screen, can still be reached and pressed by scrolling the keyboard
+
+### Requirement: Learner can hide the on-screen keyboard
+The frontend SHALL provide a setting, independent of the virtual keyboard
+setting, that lets the learner hide the on-screen keyboard entirely, and
+SHALL persist that choice across visits. A learner typing on a physical
+keyboard they already know needs no help finding the keys, and the board is
+a lot of screen to keep spending on a hint they are not using.
+
+#### Scenario: Hiding the on-screen keyboard
+- **WHEN** the learner turns the on-screen keyboard setting to "hide"
+- **THEN** the on-screen keyboard is no longer displayed
+
+#### Scenario: Typing still works while it is hidden
+- **WHEN** the on-screen keyboard is hidden
+- **THEN** the learner can still type into the answer field, whether from a
+ physical keyboard or, where enabled, the OS virtual keyboard, and checking
+ the answer works exactly as it does with the board shown
+
+#### Scenario: Showing it again
+- **WHEN** the learner turns the on-screen keyboard setting back to "show"
+- **THEN** the on-screen keyboard is displayed again, following the current
+ exercise's layout and next-key highlight as usual
+
+#### Scenario: The choice persists
+- **WHEN** the learner sets this independently of the virtual keyboard
+ setting and later reloads the application or returns in a new visit
+- **THEN** the on-screen keyboard's shown/hidden state is unchanged, and the
+ virtual keyboard setting is unaffected by it
diff --git a/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/tasks.md b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/tasks.md
new file mode 100644
index 0000000..333ba2e
--- /dev/null
+++ b/openspec/changes/archive/2026-09-11-mobile-virtual-keyboard-control/tasks.md
@@ -0,0 +1,103 @@
+## 1. Device classification
+
+- [x] 1.1 Add `src/frontend/src/lib/device.js` exporting a reactive
+ `isPhone` (or equivalent composable) backed by `matchMedia` queries for
+ `(max-width: 640px)` and `(hover: none) and (pointer: coarse)`, both
+ required for a phone classification
+- [x] 1.2 Add `src/frontend/src/lib/__tests__/device.test.js` mocking
+ `window.matchMedia` and verify: both conditions true → phone; either
+ false → not phone; a change event on the media query list updates the
+ reactive value
+
+## 2. Settings store
+
+- [x] 2.1 Add `src/frontend/src/stores/settings.js`, a Pinia store holding
+ `virtualKeyboardOverride` (`'auto' | 'on' | 'off'`, default `'auto'`)
+ and a computed `virtualKeyboardEnabled` that resolves to the override
+ when set, otherwise `!isPhone` from `lib/device.js`
+- [x] 2.2 Persist `virtualKeyboardOverride` to `localStorage` on change and
+ hydrate it from `localStorage` on store creation, with reads/writes
+ wrapped so a failure (e.g. storage disabled) falls back to `'auto'`
+ in memory instead of throwing
+- [x] 2.3 Add `src/frontend/src/stores/__tests__/settings.test.js` and
+ verify: default is `'auto'`; setting `'on'`/`'off'` updates
+ `virtualKeyboardEnabled` regardless of a mocked `isPhone`; a persisted
+ value is read back on a fresh store instance; a `localStorage` that
+ throws on get/set does not crash store creation or a setting change
+
+## 3. Suppress the virtual keyboard on the answer field
+
+- [x] 3.1 In `AnswerInput.vue`, bind `:inputmode` on the field to
+ `store.virtualKeyboardEnabled ? 'text' : 'none'` (reading the new
+ settings store), and verify existing `v-model`, `@keyup.enter`, and
+ focus-on-mount behavior in `AnswerInput.vue` are unchanged (existing
+ component tests, if any, continue to pass; otherwise a quick manual
+ check that typing and Enter-to-check still work)
+- [x] 3.2 Verify manually in a touch-emulated mobile viewport (browser
+ devtools device toolbar, or a real phone) that focusing the field no
+ longer raises the OS virtual keyboard when classified as a phone with
+ no override set
+
+## 4. Override control
+
+- [x] 4.1 ~~Add a control to `OnScreenKeyboard.vue`'s bottom bar~~ — superseded
+ by task 6.2: e2e verification at phone width found the bottom-bar pill
+ unreachable (overlapped by other elements), so the control lives in a
+ new header settings menu instead (see design.md, "Settings control
+ placement"). `settingsStore.virtualKeyboardOverride` and its three
+ states (`auto`/`on`/`off`) are unchanged.
+- [x] 4.2 Verify by keyboard/mouse interaction (Playwright, phone-width and
+ desktop-width viewports) that choosing an option changes
+ `virtualKeyboardEnabled` and that the change is reflected in
+ `AnswerInput.vue`'s `inputmode` without a reload
+ (`e2e/virtual-keyboard.spec.js`)
+
+## 5. Spec and regression check
+
+- [x] 5.1 Run the frontend test suite (`npm test` in `src/frontend/`) and
+ verify all tests pass, including the new device/settings tests
+- [x] 5.2 Walk each scenario in
+ `openspec/changes/mobile-virtual-keyboard-control/specs/typing-practice/spec.md`
+ against the running app (phone-emulated viewport, tablet-emulated
+ viewport, and override on/off/auto) and confirm each holds — covered by
+ the full e2e suite (44/44 passing) run in the Tilt dev pod, including
+ `e2e/virtual-keyboard.spec.js` and `e2e/responsive-keyboard.spec.js`
+
+## 6. The on-screen keyboard board fits a phone width
+
+- [x] 6.1 In `OnScreenKeyboard.vue`, make the panel `w-full
+ max-w-[var(--board)]` (was a fixed `w-[var(--board)]`) and wrap the
+ rows + space bar in an `overflow-x-auto` container at their full,
+ unshrunk width, so keys never shrink below their current tappable size
+ — the panel adapts to the viewport, its contents scroll sideways below
+ `--board`
+- [x] 6.2 Move the virtual-keyboard override control out of
+ `OnScreenKeyboard.vue`'s bottom bar into a new `SettingsMenu.vue`
+ (a gear button in `App.vue`'s header, opening a small popover),
+ positioned so it adds no height to the header row on any viewport
+- [x] 6.3 Add `e2e/responsive-keyboard.spec.js` and verify at a phone-sized,
+ touch-primary viewport: the page itself never scrolls sideways, and
+ the longest catalog sentence can still be typed key-by-key through the
+ on-screen keyboard, off-screen keys included
+- [x] 6.4 Update `e2e/practice.spec.js`'s DOM-depth locator for the keyboard
+ panel (one extra wrapper level from the new scroll container) and
+ re-verify the existing "fits on one screen" / "field lines up with the
+ keys" desktop-viewport assertions still pass
+
+## 7. Learner can hide the on-screen keyboard
+
+- [x] 7.1 Add `onScreenKeyboardVisible` (boolean, default `true`) to
+ `stores/settings.js`, persisted to `localStorage` the same
+ fail-soft way as `virtualKeyboardOverride`
+- [x] 7.2 Add its test coverage to `stores/__tests__/settings.test.js`:
+ defaults to shown, is read back by a fresh store instance, a broken
+ `localStorage` doesn't crash a read or a write, and it is independent
+ of `virtualKeyboardOverride`
+- [x] 7.3 In `SentenceView.vue`, `v-if` `` on
+ `settingsStore.onScreenKeyboardVisible`
+- [x] 7.4 Add a second option group to `SettingsMenu.vue` ("On-screen
+ keyboard": Show / Hide) reading and writing the new setting
+- [x] 7.5 Add e2e coverage (`e2e/virtual-keyboard.spec.js`) that hiding
+ removes the board while typing and checking still work (a physical
+ keyboard into the plain ``), and that showing it again brings
+ it back
diff --git a/openspec/changes/serve-static-and-media/.openspec.yaml b/openspec/changes/archive/2026-09-11-serve-static-and-media/.openspec.yaml
similarity index 100%
rename from openspec/changes/serve-static-and-media/.openspec.yaml
rename to openspec/changes/archive/2026-09-11-serve-static-and-media/.openspec.yaml
diff --git a/openspec/changes/serve-static-and-media/design.md b/openspec/changes/archive/2026-09-11-serve-static-and-media/design.md
similarity index 100%
rename from openspec/changes/serve-static-and-media/design.md
rename to openspec/changes/archive/2026-09-11-serve-static-and-media/design.md
diff --git a/openspec/changes/serve-static-and-media/proposal.md b/openspec/changes/archive/2026-09-11-serve-static-and-media/proposal.md
similarity index 100%
rename from openspec/changes/serve-static-and-media/proposal.md
rename to openspec/changes/archive/2026-09-11-serve-static-and-media/proposal.md
diff --git a/openspec/changes/serve-static-and-media/specs/local-dev-environment/spec.md b/openspec/changes/archive/2026-09-11-serve-static-and-media/specs/local-dev-environment/spec.md
similarity index 100%
rename from openspec/changes/serve-static-and-media/specs/local-dev-environment/spec.md
rename to openspec/changes/archive/2026-09-11-serve-static-and-media/specs/local-dev-environment/spec.md
diff --git a/openspec/changes/serve-static-and-media/specs/production-deployment/spec.md b/openspec/changes/archive/2026-09-11-serve-static-and-media/specs/production-deployment/spec.md
similarity index 100%
rename from openspec/changes/serve-static-and-media/specs/production-deployment/spec.md
rename to openspec/changes/archive/2026-09-11-serve-static-and-media/specs/production-deployment/spec.md
diff --git a/openspec/changes/serve-static-and-media/tasks.md b/openspec/changes/archive/2026-09-11-serve-static-and-media/tasks.md
similarity index 98%
rename from openspec/changes/serve-static-and-media/tasks.md
rename to openspec/changes/archive/2026-09-11-serve-static-and-media/tasks.md
index 3f08bce..8a0070c 100644
--- a/openspec/changes/serve-static-and-media/tasks.md
+++ b/openspec/changes/archive/2026-09-11-serve-static-and-media/tasks.md
@@ -32,7 +32,7 @@
- [x] 5.1 Bring the stack up in the default mode and fetch a clip through the Gateway: a `200` with `audio/mpeg`, served by the media server rather than Django
- [x] 5.2 Confirm a `Range` request returns `206` with a correct `Content-Range`, which is what seeking in `