Currently press-enter-in-cell uses highest-row/highest-col to decide where to move, coupling navigation to data extent. Intended design:
highest-row/highest-col should include the cursor's current positionpress-enter-in-cell should only increment the positionAdd a dedicated event handler for Tab (native Tab is no longer sufficient)
[ ] 1.2 Add arrow-key navigation
Implement ArrowUp, ArrowDown, ArrowLeft, ArrowRight handlers that move the cursor between cells, firing from on-keyDown (not on-keyPress). Navigation logic should live alongside Enter/Tab handling (1.1).
maxrow = 20 and maxcol = "G" are hardcoded. Decide whether the grid grows as the cursor reaches the edge or stays fixed with a scroll container. A fixed viewport with overflow scroll is likely right for the quick-calculation use case.
When a range is selected, Enter/Tab/arrow keys should cycle within the selection rather than the full table. Depends on the selection model (see section 5).
parse-variables already uppercases symbol names, but confirm end-to-end that typing =a1 + b2 in a cell evaluates correctly.
gather-variables-and-evaluate-cell returns :error when forward references are dirty, but the intended semantics vs. :insufficient-data are not fully specified. Clarify and document the distinction.
evaluate-all handles cycles correctlyevaluate-all calls evaluate-from-cell which runs find-cycle, so cycles should be caught at initialization. Confirm this is sufficient and clean up the open question.
The model processes one cell change at a time. A batch-update path will be needed for paste operations or future server sync.
denotify-references + notify-references wipe and re-add all back-references on every edit. Diffing old vs. new :refs and updating only the delta may help at scale.
find-cycleEvaluate whether a dynamic memoization strategy with cache invalidation on data change is worth the complexity.
Cover the core evaluation engine with unit tests:
sum, average, etc.):calc-error, :cycle-error, :insufficient-data)Edge cases: empty cells in ranges, self-references, deeply nested dependencies
[ ] 3.2 Integration tests for state transitions
Test re-frame event handlers end-to-end through the db:
Reference graph updates when a formula changes
[ ] 3.3 End-to-end tests
Browser-level tests covering full user flows:
= is typedWhen a cell value begins with =, display a dropdown of available math.js functions. Filter the list as the user continues typing. Selecting an item inserts the function name with a placeholder for arguments.
As the user types a column letter that could start a cell reference (e.g. A), suggest cell addresses that contain data. For ranges, trigger suggestions when : follows a cell reference.
Inside a function call, show a tooltip indicating expected arguments and their types (e.g. sum(value1, value2, ...)).
The :selection key in :position is always nil. Implement {:start {:col "A" :row 1} :end {:col "C" :row 3}} as the selection shape. Set on shift+arrow or click-drag; clear on unmodified navigation.
highlight-cells in subs.cljs already sets :view :highlighted on cells within a selection range, but the ::table-data subscription never calls it when :selection is non-nil. Connect the two so selected cells receive the highlighted CSS class.
When a numeric range is selected, display the sum and average in the UI (e.g. a status bar or floating chip). Offer to insert the result as a formula into a user-chosen cell.
When a cell or range is selected, show a compact set of floating action buttons positioned around the selection. Buttons must be large enough to tap comfortably on a phone screen. Implement in this order:
Under consideration.
A pinned row below the table showing the sum (and possibly average) of each column, updating reactively. Show only for columns that contain numbers.
Equivalent pinned column to the right of the table for row aggregates.
The current left and bottom panel slots exist but contain only placeholder content. Replace with a small, well-designed panel surfacing the most useful controls. The panel should stay visually aligned with the table as it scrolls (likely position: sticky or a scroll event listener).
A small modal (triggered from the control panel) listing and explaining:
How cell references and ranges work
[ ] 8.3 Replace "About" with a popup modal
Remove the current inline disclaimer text. Add an About button that opens a conventional modal with app info, version, and credits.
The current logo.svg is a placeholder. Design and replace with a better mark.
Ensure cells are large enough to tap accurately on mobile. Tap to select, double-tap or long-press to enter edit mode. The current on-focus/on-blur model may need adjustment for touch events.
Implement tap-and-drag or tap-then-drag-handle selection. The floating control buttons (section 6) should be comfortably tappable.
Adapt the layout to small screens: control panel sidebars should collapse by default on mobile; consider a bottom sheet instead of a side panel.
On mobile, the software keyboard resizes the viewport and may conflict with cell navigation. Ensure formula entry and cell-to-cell movement work correctly with a software keyboard open.
Longer-term.
When a user selects a range and triggers fill (via the control button in 6.7), infer the pattern (arithmetic sequence, geometric, repeated value, date sequence, etc.) and propose a continuation into adjacent empty cells.
Show a preview of the proposed fill before committing, allowing the user to accept, adjust the inferred pattern, or cancel.
Long-term consideration — not yet committed.
Save the table to the browser's localStorage on change and restore on page load. Provides durable-but-local storage with no backend required.
Extend the Express server to store table data, with considerations for:
Conflict resolution for future collaboration
[ ] 11.3 Table sharing
Allow a user to share a table with others via a link. Requires server-side persistence (11.2). Considerations:
The items below were identified from a partial read of the codebase. The remaining files should be audited for similar issues before this section is considered complete.
:inbound entriesnotify-references uses conj on a list with no deduplication. A formula like =A1 + A1 adds A1's back-reference twice. denotify-references removes only the first match via filter, leaving one stale copy. Use a set for :inbound, or deduplicate on insert.
:inbound with a concrete collectiondenotify-references stores the result of (partial filter ...) — a lazy sequence — into the app db. Lazy seqs in re-frame state can cause subtle equality and inspection issues. Replace with (into [] (filter ...)).
evaluate-allThe retry loop — (recur data (concat (rest queue) (list cur))) — re-queues cells with unsatisfied dependencies indefinitely. Cycle detection in evaluate-from-cell should prevent an infinite loop in practice, but there is no explicit guard. Add a visited set or iteration counter as a safety net.
on-keyPress with on-key-downThe cell's enter-key handler in views/sheet.cljs uses :on-keyPress, which is deprecated in both React and the underlying browser API. Move to :on-key-down.
evaluate-expression memoization cacheevaluate-expression is memoized on (expression, variables-map). Every unique combination ever computed is cached for the lifetime of the page. In a long editing session this becomes a gradual memory leak. Consider a bounded LRU cache or explicit cache invalidation.
println debug statementsEvery event handler and subscription (::table-data in particular) fires println on every call. This floods the console and makes debugging new code harder. Remove or gate behind a dev-mode flag.
maxrow = 20 and maxcol = "G" are hardcoded in views/sheet.cljs while min-max-row and min-max-col defined in db.cljs go unused. The view should read from the db values, leaving a single source of truth. (Related: 1.3.)
gather-variables-and-evaluate-cellAt ~35 lines with six boolean flags and a multi-branch cond, the function is near its readability limit. Adding another error type or evaluation mode will tip it over. Consider extracting the disqualification checks and the variable-collection step into named helpers.
create-all-references to use walk-modify-dataBoth functions perform a reduce-kv double-walk over the data map. Eliminate the duplication by having create-all-references call walk-modify-data.