287 lines
14 KiB
Markdown
287 lines
14 KiB
Markdown
# 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
|
|
|
|
### The Intel macOS and Windows on ARM builds, and the TUI on Windows, have never run
|
|
|
|
CI builds and publishes `tally-x86_64-macos`, `tally-x86_64-windows.exe` and
|
|
`tally-aarch64-windows.exe` with the other desktop targets. The Windows CLI has been run
|
|
under Wine: arithmetic, programmer mode, conversion, `tally struct` from an argument and
|
|
from stdin, errors with exit status 1, and output redirected to a file and appended to.
|
|
Still never run: the Intel macOS binary at all (it is a well-formed x86_64 Mach-O, which
|
|
is all that has been checked), the ARM64 Windows binary at all (an ARM64 PE that, like
|
|
the x64 one, imports only `ntdll.dll` and `kernel32.dll`; Wine here does not run ARM64),
|
|
and the TUI on a real Windows console. Under Wine with no console it fails cleanly with
|
|
"failed to start the TUI: InvalidHandle", which says nothing about a console that exists.
|
|
|
|
### 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.
|