# Tasks: Tally - Cross-Platform Calculator ## References - #[[file:.kiro/specs/calculator/requirements.md]] - #[[file:.kiro/specs/calculator/design.md]] - #[[file:.kiro/specs/calculator/history.md]] Open work only. Each item has a name rather than a number, so finishing one renumbers nothing; when an item is done, delete it. The record of what was done and why is `history.md`, where every finished task and numbered open item from before this file was split still lives under the name the code and the other documents cite it by ("Task 5.19", "open item 10"). --- ## Engine ### `Ans` is a pseudovariable, and the engine models it as a constant FR-1.6.1; was open item 16. Task 5.20 stopped `Ans = 5` from being silently discarded, but did it by folding `Ans` in with `pi`, `e` and `tau` behind one `Environment.isBuiltIn`, and the error it raises is `AssignmentToConstant`, "cannot assign to a built-in constant". `Ans` is not a constant: it changes on every evaluation. - The name and the message. A name the engine writes and the user only reads wants its own error, or a shared one worded to cover both, so `Ans = 5` does not claim `Ans` is constant. - The read path. `getVar` special-cases the two spellings inline, ahead of the variable map and next to the constant table. A pseudovariable is a third kind of name and reads as one only if it is declared as one. Scope is the immediately preceding answer and nothing else: no `Ans2`, no indexed history. Earlier results stay FR-1.7's replay. ### Unit arithmetic inside expressions FR-4.2: `5 kg + 3 lb` evaluates in the left-hand unit. Unlike FR-4.1, which needs only the `to` keyword as a trigger, this needs unit-aware tokenizing and evaluation. Design 4.2 sketches the approach. ### The 40-digit display cap is provisional NFR-7: the integer-digit cap above which an exact value's display abbreviates to scientific notation is "PROVISIONALLY 40 digits, pending review", chosen as the smallest round number above 2^128 and not from any measured preference. It is now each frontend's `FormatOptions.max_integer_digits` (Task 5.17), so the review is per frontend: the CLI and the TUI both say 40, the Android calculator its own. --- ## Display and clipboard ### The exact fraction is never shown Was open item 13. NFR-9.9 ends with "the exact fraction available", and no frontend asks for it. `Rational.toFractionString` works and is tested, but its only callers are tests: nothing renders `1/3` as a fraction. Wiring it needs a display decision that does not exist yet: integers and inexact values have no useful fraction form, so the frontend has to choose when to show the line at all. ### Copying a result, and marking one approximate Was open item 14. NFR-7 asks for a raw (unseparated) form for the clipboard on every frontend, and NFR-9.9 for approximate results to be marked. Android has both: a long-press copies, and the converter marks inexact rows. The terminal frontends have neither: - The TUI has no yank command and draws no approximate marker, though `Number.render` reports `truncated` and takes clipboard options. `display_format`'s doc comment in `src/tui.zig` points at a `clipboard_format` that does not exist. - The CLI has no `--raw` (NFR-7). FR-6.9 removed `--json` until it can print the C ABI's encoding rather than a second one of its own. --- ## TUI ### Bracketed paste submits a line at a time Was open item 20. vxfw turns bracketed paste on, and the TUI drops `paste_start` and `paste_end` (`typeErasedEventHandler`'s `else`), so a pasted newline arrives as Enter: pasting a two-line expression evaluates the first line and leaves the second in the prompt. Small and mode-independent: while a paste is open, Enter inserts a space. The struct view needs it first, since a pasted struct is several lines. ### History stops showing new results, and never frees what it held Was open item 8, with its other half from item 18. `drawHistory` flattens the history oldest-first into 512 line slots and slices the tail, so results stop appearing after roughly a hundred entries with detail lines. The fix is small now: fill the window newest-first, bounded by the rows actually visible, so the cap limits how far back a very tall terminal can show rather than how long a session can run. History memory is also never reclaimed: Ctrl-L frees into an arena. Task 5.8 also left the mouse wheel unable to scroll history. ### Programmer mode's value zone From open item 18, and FR-7.3's note. - `ProgField.expression` is a dead focus stop: Up/Down cycles through it, but no row is drawn for it, no key does anything in it (`handleValueInput` has an empty prong, `fieldBitStep` returns 0) and nothing highlights it. Drop it, or make it hand focus back to the input line, which is what backtick and `focus_input` already do. - The decimal fields cannot be corrected or signed. There is no backspace, and `handleValueInput` treats DEC(s) and DEC(u) alike as unsigned multiply-and-add, so typing into DEC(s) while it shows a negative number operates on the two's complement bits. There is no way to type a minus sign. - The value-zone status hint advertises `Enter:set`, and Enter does nothing there; Space is the only toggle (FR-7.3). ### Logic `src/tui.zig` holds twice From open item 18. - The bit grid's geometry: `handleValueZoneKey` computes `bits_per_row = if (width > 32) 32 else width` for Up/Down, and `programmer.zig`'s `drawBitGrid` the same for drawing. Change one and the arrows disagree with the grid. - The float-view toggle: `applyAction`'s `.toggle_float` and `handleKey`'s Ctrl-F both flip `float_view_active`, call `syncFloatWidth` and set `prog_field = .bits`, and the bit cursor is clamped three times over. One `toggleFloatView` would hold it. - `display_format` is the same five numbers in `src/tui.zig` and `src/cli/format.zig`, with the same rationale. Per-frontend choice is the requirement (NFR-9.9), so this may be right, but it should be a decision rather than a coincidence. - Smaller: the file header lists three of the six view files it imports; `App.env` and `init` spell `engine.evaluator.Environment` where `engine.Environment` exists; `modeIndex` wraps `@intFromEnum` for two callers; `Mode` is private while `Action`, `nextMode` and `prevMode` are public and name it; `loadAnsIntoProgrammer` bounds its integer test with the literals `-9223372036854775808.0` and `18446744073709551616.0`; and `handleKey` is a 200-line if-chain where a new binding can land in the wrong order. ### Themes FR-7.9.3; was open item 17. `src/tui/draw.zig` declares `C`, eleven Molokai colours as literal RGB triples, and every view reads them directly (`C.cyan`, `C.pink`, `C.sel`), so the palette is a compile-time fact: no light theme, no following the terminal's own colours, no user choice. The names are also the palette's rather than the interface's: views ask for `C.pink` where they mean "an error" and `C.yellow` where they mean "the focused field". Two steps, in order: name the roles the views use (error, focus, selection, muted, accent), then make the mapping from role to colour a value the app holds rather than a set of constants. Only then is the palette data - a default, a file or a flag. Doing the second without the first just moves the same eleven names behind a pointer. ### Vi- and Emacs-style input FR-7.10, and Task 5.8's remainder: the prompt is a plain text field. Deferred, not dropped. --- ## Struct layout (on trial) Stage 1, the engine and `tally struct`, is Task 2.1. In rough order of value: ### Use it, then decide If `tally struct` is not reached for, the feature comes out whole: two engine files, the clang check (`engine/test/struct_oracle.zig` and its block in `build.zig`), the subcommand, and FR-3. That is easier before anything else depends on it. ### The TUI view FR-7.4. Decided while planning it, before it was deferred: a programmer sub-view on Ctrl-S rather than a fifth mode, with `float_view_active` becoming a three-way `prog_view` enum so the two sub-views cannot both be on; and bracketed paste fixed first. The CLI's table and map (design 7) are a better starting point than the sketch in design 8.3, not least because the TUI draws one byte per cell and cannot show box or block glyphs. The editing model (paste a definition, add a member, reorder to watch the padding move) is a guess until the CLI has been used. ### A C ABI export, then the Android screen `tally_struct_layout`, with JSON shaped like the CLI's table, then FR-8.6 and design 9.4. ### Smaller follow-ups - Decoding bytes through a struct, which is where endianness (FR-3.8) earns its place: a hex string, or the programmer register, read as each member's value. - Rust `#[repr(C)]` and Zig `extern struct` front ends (FR-3.13). Zig's own `@offsetOf` would be the oracle for the second. - A "reorder by alignment" line: the size of the same members sorted by descending alignment, which is the question padding usually prompts. - Types left out by decision rather than difficulty, each needing clang cases of its own: `long double` (which would split `sysv64`, since it is 16 bytes on x86-64 and 8 on AArch64 macOS), `_Atomic` (which raises alignment on i386), and a bare `aligned` (the target's largest alignment, which no profile records). - An i386-Windows profile: MSVC aligns 8-byte types to 8 there, unlike i386 Linux, and it would exercise the Microsoft rules on a 32-bit target. - Out of scope unless a real need appears (FR-3.12): nested structs and unions, bitfields, typedefs and macros from elsewhere. That is where a C front end turns into a C compiler, and `zig cc` is already one. --- ## Android ### The financial screen FR-8.7, design 9.5. Needs C ABI payloads for the forms and the amortization schedule first (Task 6.0 left them open: "better decided against a screen than in advance"). Design 9.5's wireframe has a Calculate button, which the TUI dropped because its results are live; expect it to be redrawn from a running screen, as 9.2 was. The plan as first written: - A scrollable chip row for the calculation: CAGR, TVM, compound interest, present value. - A card-based form adapting to it; for TVM, five fields with the blank one solved for. - A result card with an expandable formula breakdown, and long-press to copy. ### The rest of programmer mode Task 6.4 shipped a first version without history, the float view, ASCII literals (`'ELF'`) and the system keyboard. None needs the engine to change except the float view, which needs a float-view payload in the C ABI (Task 6.0). ### Polish FR-8.9, FR-8.11, FR-8.12 and the rest of Task 6.8: - A two-pane layout for tablets (input left, results right). - Landscape layouts: a wider bit grid, side-by-side panels. - Haptic feedback on bit toggles and key presses. - A splash screen. - `sqrt` drawn as a radical in the calculator's expression, rather than as `sqrt(` (design 9.2). - Rotation: check that every screen survives it. State is saved on every edit, which should cover it, but nobody has rotated each screen and looked. ### Checks not yet made on a device TalkBack on every screen, though every symbol key carries a spoken label; a real on-screen keyboard's dismissal bringing the pad back on the calculator (the emulator's IME showed its hardware-keyboard toolbar, so only Enter was exercised); the light theme on the converter after its palette change; and the converter migrating the single value an older build saved, which is unit tested and has never run on a device (an instrumented run uninstalls the app first). --- ## Build, CI and release ### Windows and Intel macOS are built by nobody and run by nobody NFR-2 names Windows x86-64 and macOS x86-64 alongside the targets CI builds (x86_64 and aarch64 Linux, aarch64 macOS). `zig build -Dtarget=x86_64-windows-gnu` and `-Dtarget=x86_64-macos` both compile today; neither is in CI's release builds, and neither binary, nor the TUI on a Windows console, has ever been run. ### Tests that run the binary Task 7.2's first item: nothing runs `tally` and checks its stdout, stderr and exit status. Every CLI test calls the formatters directly, which is how redirected output was overwriting its own first byte for as long as it was (Task 5.30): the defect lived in `main`, between the formatter and the file, where no test looked. A test that runs the binary with stdout sent to a file would have caught it. Probably a build step that runs the installed binary over a table of invocations, the way `jvm-test` runs Java. ### The coverage gate runs only before a commit Task 7.3: `zig build coverage -Dcoverage-threshold=80` is a pre-commit hook and not a CI step ("wttr.in has the same step disabled in CI; not investigated"). ### The TUI's Cobertura report loses `src/tui.zig` kcov writes the TUI report's sources relative to `src/tui/`, and `src/tui.zig` sits outside that root, so `cobertura.xml` names it `zig`. Queries by path find nothing for the file with most of the TUI's code; `sonarqube.xml` in the same report has the absolute path and answers correctly. Either the report's source root (`build/Coverage.zig`) or the include paths could fix it. --- ## Documentation ### `CONTRIBUTING.md` Task 7.4: dev setup, an architecture overview, and how to add an ABI profile (now a table entry in `struct_layout.zig` plus clang targets in `build.zig`). The struct syntax reference is `tally struct --help` and FR-3.1/FR-3.12, and wants a README section if the feature stays.