Skip to content

Commit a290fd6

Browse files
committed
[docs] Freeze line numbers and editor zoom
Signed-off-by: Viwat Vchirawongkwin <viwat.v@chula.ac.th>
1 parent 0c64bc7 commit a290fd6

3 files changed

Lines changed: 14 additions & 10 deletions

File tree

‎docs/specifications/App/TDD.md‎

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,9 @@ abstract interface class BleSession {
190190

191191
**Responsibility:** Python/MicroPython syntax highlighting, line numbers, auto-indent, multi-file tabs, Save/Run/Stop actions, find (and find/replace), external-keyboard shortcuts, tablet-friendly touch targets. The editor widget is `flutter_code_editor`, behind an `EditorSurface` interface with a WebView-hosted editor (Monaco/CodeMirror) as the documented fallback ([PRD §16.1](../prd.md), [OI-1](specs.md)).
192192

193-
**Public API (sketch):** `EditorSurface { String get text; set text; Stream<EditEvent> edits; void applyFind(...); }`; an `EditorTabs` controller backed by Drift `project_files`.
193+
**Public API:** an app-owned `EditorSurface` push-in/stream-out widget contract: the caller supplies the current text, change callback, editor text style, localized hint, and Signal colors. The default factory builds the rich `flutter_code_editor` adapter; tests and the R1 escape hatch can select the retained plain-field adapter without changing feature code. A later `EditorTabs` controller is backed by Drift `project_files`.
194194

195-
**Key state:** open buffers (dirty flags), active tab, find/replace state. Save persists to Drift (D4); Run uploads then runs through `Connection` (the §8.2 loop).
195+
**Key state:** the volatile current buffer (dirty flag) plus session-wide editor display settings. Font size defaults to 14 sp, moves in one-point steps over the inclusive 10–24 sp range, and applies only to code plus its line-number gutter; the platform `TextScaler` still composes over it. A-24 hydrates that display value through the single Drift settings store. Open buffers, active tab, and find/replace state land with the full persistent editor. Save persists to Drift (D4); Run uploads then runs through `Connection` (the §8.2 loop).
196196

197197
**Dependencies:** `Connection` (run/save), `lib/data/` (persist), `lib/localization/`. No `lib/ble/`.
198198

@@ -984,9 +984,11 @@ those semantics early.
984984

985985
### 11.1 Editor
986986

987-
`flutter_code_editor` behind the `EditorSurface` interface ([§4.3](#43-libeditor--code-editor)), with a WebView-hosted editor (Monaco/CodeMirror) as the documented fallback selectable behind the same interface ([PRD §16.1](../prd.md), [OI-1](specs.md)). Python highlighting, line numbers, auto-indent (FR-EDIT-1); tabs + Save/Run (FR-EDIT-2); find, optional find/replace (FR-EDIT-3); external-keyboard shortcuts for Save/Run/Stop/comment/find (FR-EDIT-4); large touch targets, no phone breakage (FR-EDIT-5). Save persists to Drift; Run uploads then runs through `Connection` (FR-EDIT-7).
987+
`flutter_code_editor` 0.3.5 behind the app-owned `EditorSurface` interface ([§4.3](#43-libeditor--code-editor), [ADR-0012](../../decisions/0012-flutter-code-editor-behind-editorsurface.md)), with the existing plain `TextField` retained as the immediate fallback and a WebView-hosted editor (Monaco/CodeMirror) remaining the documented later fallback ([PRD §16.1](../prd.md), [OI-1](specs.md)). Python highlighting, line numbers, auto-indent (FR-EDIT-1); tabs + Save/Run (FR-EDIT-2); find, optional find/replace (FR-EDIT-3); external-keyboard shortcuts for Save/Run/Stop/comment/find (FR-EDIT-4); large touch targets, no phone breakage, and editor-only font adjustment that preserves system scaling (FR-EDIT-5). Save persists to Drift; Run uploads then runs through `Connection` (FR-EDIT-7).
988988

989-
**Working-loop increment — FROZEN (A-20 subset · `[docs]` 2026-07-02, [ADR-0010](../../decisions/0010-working-loop-in-memory-document.md)).** The first shippable editor is a **monospace editable surface** (a `SignalCodeColors`-styled text field, line numbers if cheap — no `flutter_code_editor`, no `google_fonts`, NFR-OFF dep hygiene) over a **single volatile in-memory current document** ([§11.6](#116-working-loop-provider-contract-frozen)). It offers **New** and (when connected) **Run**. **Deferred to A-24 / OI-1:** syntax highlighting, multi-file tabs, find/replace, external-keyboard shortcuts (FR-EDIT-1/2/3/4), and Drift persistence — Save-to-project (FR-EDIT-7) and buffers-survive-restart (NFR-REL-3/FR-PROJ-6) are **carried, not dropped**, and re-freeze at A-24. The document provider's `openFromBoard`/`markSaved` are the persistence hooks A-24 rebinds onto Drift without a widget rewrite.
989+
**Working-loop increment — FROZEN (A-20 subset · `[docs]` 2026-07-02, amended 2026-08-22 by [ADR-0012](../../decisions/0012-flutter-code-editor-behind-editorsurface.md), [ADR-0010](../../decisions/0010-working-loop-in-memory-document.md)).** The shippable editor keeps the **single volatile in-memory current document** ([§11.6](#116-working-loop-provider-contract-frozen)) while moving its presentation behind `EditorSurface`. The default rich adapter supplies Python highlighting and an always-visible, one-based, vertically synchronized line-number gutter; only line numbers are enabled in the gutter (package errors and folding handles stay off), package find/autocomplete UI stays off, and no network analyzer is reachable from `lib/editor`. The plain `TextField` remains executable as the R1 fallback. Both paths preserve the smart-punctuation invariant, and adapter-owned Tab handling must indent a multi-line selection without replacing it.
990+
991+
The same bounded increment adds session-wide editor zoom: default 14 sp, inclusive 10–24 sp, one-point steps. Code and gutter share the selected family/size/height and update immediately; changing it preserves document bytes, selection, undo history, dirty state, and board path. Controls remain accessible at phone width and compose with the full platform text scaler. Durable display preference, syntax-editor tabs, app-owned localized find/replace, the remaining shortcuts, Save-to-project (FR-EDIT-7), and buffers-survive-restart (NFR-REL-3/FR-PROJ-6) remain carried to A-24 or later. The document and display providers are the hooks A-24 rebinds to the one Drift `SettingsStore`; no parallel preferences backend is admitted.
990992

991993
### 11.2 Blocks
992994

@@ -2165,11 +2167,11 @@ Design element → satisfied requirement IDs. Each `FR-*`/`NFR-*`/`CON-*`/`DAT-*
21652167
- **NFR-PERF-3/4** and **OI-4** have design *levers* (MTU 247, window `W`, bounded console buffer, time-to-connect via saved `board_ref`) but the concrete ceilings are HIL-frozen later — by intent, not a gap.
21662168
- **BLD-1/2/3/4/6/10/11/12** (single Flutter codebase, `pubspec` governance + lock, SemVer, free dual-store-at-parity distribution, generated notices + in-app Open-Source Notices screen / IF-6, platform launcher packaging, store-signable embedded data, and fail-closed Android upload signing) are project/build-pipeline obligations: this TDD honors them (single codebase, ASCII identifiers, notices screen surfaced in UI, shared vector launcher source, deterministic Blockly asset transform, and the signed AAB gate) but the release pipeline is owned by the build/infra stories (X-03/X-11). Flagged here for completeness.
21672169
- **BLD-9** (previous-protocol-major compatibility window) is forward-looking: no PBLE/2 exists yet, but its design home is the version-negotiation seam ([§8.6](#86-hello--capabilities)) — the same HELLO `proto_versions[]` exchange that refuses an unsupported version (FR-PBLE-5/6) is where a future app would select a previous major, so deployed boards are never bricked by an app update.
2168-
- **OI-1** (editor widget) and **OI-2** (state-management ADR) are reflected as D3 and the `EditorSurface` fallback ([§11.1](#111-editor)); both await their ADRs ([§17](#17-risks--open-questions)).
2170+
- **OI-1** (editor widget) is closed by ADR-0012, and **OI-2** (state management) is closed by ADR-0007. Their choices are reflected in the Riverpod state graph and the executable `EditorSurface` fallback ([§11.1](#111-editor)); R1 remains a measured runtime risk rather than an undecided architecture.
21692171

21702172
## 17. Risks & open questions
21712173

2172-
- **R1 — Flutter Python-editor widget maturity (OI-1).** `flutter_code_editor` tablet/external-keyboard maturity is unproven. Mitigation: the `EditorSurface` interface lets a WebView-hosted Monaco/CodeMirror editor drop in without touching callers ([§11.1](#111-editor)); decision pending evaluation and an ADR.
2174+
- **R1 — Flutter Python-editor widget residual risk (OI-1 closed).** ADR-0012's evaluation found a dormant upstream, superlinear whole-buffer work, and an unsafe upstream Tab binding. PyBLE pins 0.3.5 behind `EditorSurface`, owns Tab interception and smart-punctuation normalization, disables the optional network/error/find/autocomplete surfaces, and retains the plain-field implementation. Switch to that live fallback if target-iPad testing finds an adapter-irreparable correctness/data-loss defect, realistic beginner files become unusably slow, or a Flutter SDK upgrade leaves the package incompatible; a later WebView-hosted Monaco/CodeMirror surface can use the same feature seam ([§11.1](#111-editor)).
21732175
- **R2 — State-management choice (OI-2).** D3 fixes a single approach (working choice Riverpod); must be ratified by an ADR before broad adoption (NFR-MAINT-2).
21742176
- **R3 — iOS vs Android BLE behavior.** MTU negotiation timing/results differ; iOS hides the MAC (use the platform identifier for `board_ref`); permission models diverge. Isolated in `lib/ble/` ([§7.4](#74-permissions--platform-quirks)) and tested with a mocked transport (FR-BLE-7) plus per-chip on-device smoke.
21752177
- **R4 — Blockly-in-WebView.** WebView/JS-channel performance and offline asset bundling on tablets are a risk; the bridge stays behind `Connection` so a failure is contained to `lib/blocks/` ([§11.2](#112-blocks)).

‎docs/specifications/App/design-system.md‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,8 @@ Material 3 type scale on the **default sans** (Roboto / platform default — no
157157

158158
**`code` role (`SignalType.code`, offline mono).** `TextStyle(fontFamilyFallback: ['Menlo', 'monospace'], fontSize: 14, height: 1.45)`; apply the color via `SignalType.codeOn(scheme)` (`onSurface`) or a `SignalCodeColors` stream color.
159159

160+
**Editor zoom — FROZEN (`[docs]` 2026-08-22, ADR-0012).** The shared `code` token remains 14 sp so console output and technical identifiers do not change. The editor alone derives a copy with a session-selected size: default 14 sp, inclusive 10–24 sp, one-point steps. The one-based gutter uses the identical font family, size, and line height with `SignalCodeColors.gutter`, so numbers stay aligned as the editor scrolls. Wide editor chrome exposes the current numeric value plus ≥ 48 dp decrease/increase actions; constrained layouts may collapse those into one localized, accessible size menu rather than overflow. The lower/upper action disables at its bound. This preference never marks or rewrites the document and is not durable until A-24's Drift `SettingsStore`; no second settings backend is introduced.
161+
160162
- **Apple-platform correctness (folded from review).** The family list leads with **`Menlo`** (the guaranteed-installed mono on iOS/iPadOS), then the Android generic **`monospace`**. It deliberately does **not** name `monospace` first: on iOS/iPadOS the generic can resolve to the default *proportional* face (breaking indentation/column alignment on a first-class target, NFR-COMPAT-1), whereas `Menlo` always resolves there. On Android `Menlo` is absent, so resolution falls through to the real `monospace`.
161163
- **Offline-first guardrail.** The list contains only genuinely-resolvable families (Apple `Menlo`, Android `monospace`); `RobotoMono`/`Consolas` were **removed** (not bundled by Flutter, silent no-ops that invite a `google_fonts` "fix"). The list must **not** be completed with `google_fonts` or any runtime/network font. If a guaranteed iPad mono glyph ever proves necessary, **bundle a mono font as a local asset** (offline) and record its license in `app/DEPENDENCIES.md` — never `google_fonts`.
162164

@@ -166,7 +168,7 @@ paths, PBLE/1 status names — never localized, FR-I18N-4). The pin-reference pa
166168
renders target identifiers in this role and falls back to generic board-
167169
documentation guidance for an unknown value.
168170

169-
**Respect text scaling:** never fix heights that clip at large `textScaleFactor`; the shell's no-overflow contract holds to `textScaleFactor 2.0` (§9). Purely-cosmetic chrome text (the toolbar wordmark and the pill label) may clamp its scaler to ≤ 1.3 so the top chrome stays bounded while body/content text scales fully.
171+
**Respect text scaling:** never fix heights that clip at large `textScaleFactor`; the shell's no-overflow contract holds to `textScaleFactor 2.0` (§9). Editor zoom selects the base code size and the platform `TextScaler` then applies in full to both code and gutter; zoom MUST NOT replace, disable, or clamp accessibility scaling. Purely-cosmetic chrome text (the toolbar wordmark and the pill label) may clamp its scaler to ≤ 1.3 so the top chrome stays bounded while body/content text scales fully.
170172

171173
---
172174

‎docs/specifications/App/specs.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,11 @@ The client implements the PBLE/1 wire contract; it references [protocol.md](../p
159159
- **FR-EDIT-2** — The editor MUST offer Save and Run actions and MUST support multiple open files via tabs. MUST (*source: PRD §9.3; verify: widget; story: A-20*)
160160
- **FR-EDIT-3** — The editor MUST provide find and SHOULD provide find/replace. MUST/SHOULD (*source: PRD §9.3; verify: widget; story: A-20*)
161161
- **FR-EDIT-4** — The editor SHOULD support an external keyboard with common shortcuts (Save, Run, Stop, comment/uncomment, find). SHOULD (*source: PRD §9.3, §13.7; verify: widget; story: A-20*)
162-
- **FR-EDIT-5** — Touch targets MUST be tablet-friendly and the editor MUST remain usable on a phone without layout breakage. MUST (*source: PRD §9.3, §13.2; verify: golden; story: A-20*)
162+
- **FR-EDIT-5** — Touch targets MUST be tablet-friendly and the editor MUST remain usable on a phone without layout breakage. The editor MUST expose a bounded, editor-only code-font adjustment that composes with (and never replaces or clamps) the platform text scaler; line numbers MUST track the selected code size. MUST (*source: PRD §9.3, §13.2, §13.7; verify: widget/golden; story: A-20*)
163163
- **FR-EDIT-6** — The editor MUST bind only to the `Connection` API for board actions and MUST NOT import `lib/ble/`. MUST (*source: PRD §9.3, §6.1, app.md §1; verify: unit; story: A-20*)
164164
- **FR-EDIT-7** — Save MUST persist the edited file to the local project (offline-first); Run MUST upload then run through `Connection` per the §8.2 loop. MUST (*source: PRD §8.2, §9.1, §9.3; verify: integration; story: A-20/A-11*)
165165

166-
**Working-loop increment — FROZEN (A-20 subset · `[docs]` 2026-07-02, [ADR-0010](../../decisions/0010-working-loop-in-memory-document.md), [TDD §11.1](TDD.md#111-editor)/[§11.6](TDD.md#116-working-loop-provider-contract-frozen); run path superseded 2026-07-27 by [ADR-0013](../../decisions/0013-clean-room-blockly-one-way-file-backed.md)).** In scope: a monospace editable surface (`SignalCodeColors`-styled, no heavy code-editor dep) over a single volatile in-memory current document (name/content/dirty/boardPath), with **New** and (when connected) **Run**. Save and Run capture one normalized document snapshot; transfer completion marks it saved/bound only when the live document still equals that snapshot, so mid-transfer edits or a replacement document remain dirty. Run now uses the shared file-backed `putFile → runFile` action required by FR-EDIT-7 and shared with Blocks; `runSource` remains only for explicitly bounded snippets. The editor binds to `Connection` only (FR-EDIT-6, CON-8). **Deferred to A-24 / OI-1:** syntax highlighting, multi-file tabs, find/replace, external-keyboard shortcuts (FR-EDIT-1/2/3/4). FR-EDIT-7 Save-to-project and NFR-REL-3/FR-PROJ-6 buffers-survive-restart are **carried, not dropped** — the document provider's `openFromBoard`/`markSaved` are the A-24 persistence hooks; until then the buffer is volatile (RECORDED deviation, ADR-0010).
166+
**Working-loop increment — FROZEN (A-20 subset · `[docs]` 2026-07-02, amended 2026-08-22 by [ADR-0012](../../decisions/0012-flutter-code-editor-behind-editorsurface.md), [ADR-0010](../../decisions/0010-working-loop-in-memory-document.md), [TDD §11.1](TDD.md#111-editor)/[§11.6](TDD.md#116-working-loop-provider-contract-frozen); run path superseded 2026-07-27 by [ADR-0013](../../decisions/0013-clean-room-blockly-one-way-file-backed.md)).** In scope: the ADR-0012 `EditorSurface` seam with `flutter_code_editor` 0.3.5 as the default rich adapter and the existing plain `TextField` as a live fallback; Python highlighting, always-visible one-based line numbers, safe auto-indent/Tab handling, and an editor-only 10–24 sp font adjustment in one-point steps (default 14 sp). Code and gutter resize together, platform text scaling still applies, and resizing MUST NOT modify the document, selection, dirty state, or board path. The size is app-session state, independent of file and board, until A-24's single Drift `SettingsStore` makes it durable; a second preferences store MUST NOT be introduced. The single volatile in-memory current document (name/content/dirty/boardPath), **New**, and board-backed Save/Run behavior remain as frozen by ADR-0010/0013. Save and Run capture one normalized document snapshot; transfer completion marks it saved/bound only when the live document still equals that snapshot, so mid-transfer edits or a replacement document remain dirty. The editor binds to `Connection` only (FR-EDIT-6, CON-8), and editor source MUST NOT reach a network analyzer. **Still deferred to A-24 or later:** multi-file tabs, app-owned localized find/replace, the remaining external-keyboard shortcuts, durable display settings, Save-to-project, and buffers-survive-restart. The document provider's `openFromBoard`/`markSaved` and the session display provider are the A-24 persistence hooks (RECORDED deviations, ADR-0010/0012).
167167

168168
### 4.7 Console Panel (`lib/console/`) — FR-CONSOLE
169169

@@ -1128,7 +1128,7 @@ The local Drift database ([PRD §12.1](../prd.md), `lib/data/`) MUST model exact
11281128

11291129
## 12. Open Items
11301130

1131-
- **OI-1 — Flutter Python-editor widget choice.** `flutter_code_editor` tablet-keyboard maturity is a tracked risk; a WebView-hosted editor (Monaco/CodeMirror) behind the same editor interface is the documented fallback. Decision is pending evaluation (see [PRD §16.1](../prd.md), [§23](../prd.md), [§25](../prd.md)). Affects FR-EDIT-1.
1131+
- **OI-1 — Flutter Python-editor widget choice. CLOSED (2026-07-25) by [ADR-0012](../../decisions/0012-flutter-code-editor-behind-editorsurface.md).** `flutter_code_editor` is pinned exactly at 0.3.5 behind the app-owned `EditorSurface` seam, with optional network/error/find/autocomplete surfaces disabled, adapter-owned safe Tab handling, and the plain `TextField` retained as a live fallback. Tablet maturity remains the bounded R1 risk in [TDD §17](TDD.md#17-risks--open-questions), with explicit fallback triggers rather than an open widget decision.
11321132
- **OI-2 — State-management library. CLOSED (2026-07-02) by [ADR-0007](../../decisions/0007-riverpod-state-management.md).** The single declarative approach is fixed as **Riverpod** (`flutter_riverpod`), with Bloc the documented alternative (App [TDD.md §2.3](TDD.md#23-single-declarative-state-management-riverpod), D3). NFR-MAINT-2 is satisfied; the shell/A-05 provider set (`connectionProvider`, derived `connStateProvider`) wires `Connection` at the `ProviderScope` root, overridable with `FakeConnection` in tests.
11331133
- **OI-3 — PBLE/1 freeze dependency. RESOLVED (2026-07-01).** [protocol.md](../protocol.md) **§2–§10 are FROZEN for v1.0** (§2 GATT/UUIDs + §3 framing at G0; §4 opcodes + §8 status + §5/§6/§7/§9/§10 at G1, all `[docs]` 2026-07-01) — see the protocol.md per-section freeze ledger. The UUID base (`7079626c-…`), the frame/fragmentation bytes, and the opcode/status numbers are now **stable inputs**; the FR-PBLE-* client dependency on §2/§4 being frozen first (per SDD, [PRD §1B.1](../prd.md)) is **met**. (The residual label / identify **payload encodings** — `SET_LABEL` max byte-length, `SET_IDENTIFY_LED` GPIO+active-level, `IDENTIFY` blink bound — are tracked by protocol.md **OI-6** and freeze before their S3/S4 identity stories, not needed for S1/S2.)
11341134
- **OI-4 — HIL-frozen performance ceilings.** Concrete console-latency and throughput ceilings (NFR-PERF-3, NFR-PERF-4) and time-to-connect targets MUST be measured on hardware and frozen alongside [PRD §10.13](../prd.md)/[§20](../prd.md).

0 commit comments

Comments
 (0)