Skip to content

feat(side-panel): pin the import table columns up to Billable - #127

Merged
Cyclodex merged 5 commits into
mainfrom
claude/bexio-extension-issue-12-4ad9db
Aug 5, 2026
Merged

feat(side-panel): pin the import table columns up to Billable#127
Cyclodex merged 5 commits into
mainfrom
claude/bexio-extension-issue-12-4ad9db

Conversation

@Cyclodex

@Cyclodex Cyclodex commented Aug 5, 2026

Copy link
Copy Markdown
Member

Closes #12.

Problem

The ManicTime import table grows one column per tracked day. Booking an entry near the end of the month scrolls the identifying columns off screen, so you click a ▶️ button without seeing which row it belongs to. The header row already stays put vertically; the horizontal axis had nothing.

What changed

Everything left of the first date column now stays pinned while the date columns scroll underneath: #, Template, every Tag n, Notes and Billable. A header without any date column pins nothing.

  • frozenColumns.ts — new pure function, decides which columns are pinned and at which left offset. Widths are fixed constants, so the offsets are a running sum instead of a DOM measurement, which keeps them testable in jsdom. DATE_COLUMN_REGEX moved here from ImportEntriesTableCell, which now imports it — both places have to agree on where the frozen block ends.
  • ImportEntries.css — the position: sticky rules, a shadow marking the right edge of the block, and box-sizing: border-box on pinned cells (without it a cell renders 11 px wider than its width constant and each column overlaps its left neighbour).
  • The even-row stripe becomes opaque. It was #3276b44a; a translucent background lets the scrolling cells bleed through the pinned ones. Replaced by the already-blended #a9c6df / #2e4358, so the rendered colour is unchanged — including in the Templates table, which the same unscoped rule styles.

Known limitation

The pinned block is as wide as the columns it contains — a four-tag import is 546 px, wider than a narrow side panel. Documented in the "How to use this" alert and in CLAUDE.md: drag the panel wider.

Testing

  • test/frozenColumns.test.ts (new, 6 cases) pins the arithmetic and the edge cases: no date column, empty header, header starting with a date, slash-separated dates.
  • test/importEntries.test.tsx (2 new cases) checks the wiring — header, data rows and footer row all carry the offsets, date columns carry none.
  • npm run typecheck, npm run lint, npm run test:fast (256 tests) all pass.
  • jsdom has no layout, so the visual half was verified by rendering the real component output with the real CSS in Chrome and measuring: widths exactly 32/110/90/90/90/170/44, offsets exactly 0/32/142/232/322/412/582, zero gap and zero overlap between pinned cells, the first date column scrolling underneath, the sticky header still working, and all 35 pinned cells fully opaque in both light and dark. A manual step for this is now in docs/architecture/testing.md.

Design: docs/superpowers/specs/2026-08-05-frozen-import-columns-design.md.

The ManicTime import table grows one column per tracked day, so booking an
entry near the end of the month scrolled the identifying columns off screen
and the row a ▶️ button belonged to was no longer visible.

Everything left of the first date column now stays pinned while the date
columns scroll underneath: #, Template, every Tag column, Notes and Billable.
Widths are fixed constants, so the left offsets are a plain running sum
instead of a DOM measurement.

The even-row stripe becomes opaque (same rendered colour) — a translucent
background would let the scrolling cells bleed through the pinned ones.

Closes #12
…only the table

The fixed column widths clipped tag names even when the panel had room to
spare, and the panel itself carried the horizontal scrollbar.

Columns now keep their natural widths and nothing clips, so a value is never
cut off — long values wrap instead. That makes the offsets unknowable ahead of
time, so useFrozenColumnOffsets measures the header and publishes them as
--frozen-left-<position> custom properties on the table.

The table gets its own scroll container, which means the panel no longer
scrolls sideways and the pinned columns stick to the table's edge. Its
max-height is what keeps the header row sticky, since a sticky element sticks
inside its scroll container.

body is no longer a flex container (a Vite template leftover): as a flex item
#root was sized by its content, so the wide table pushed the whole panel wider
regardless of the wrapper's overflow.
The "How to use this" list is long enough to push the import table itself off
screen. It is a Collapse panel now, closed by default, so it only costs its
header row until it is asked for.
"Delete saved data" sat on its own line below the table. It now shares the row
with the auto-map button and the notes switch, pushed to the far right — one
row of controls above the table instead of two blocks around it, and the
destructive action stays visually apart from the two next to it.
@Cyclodex
Cyclodex merged commit 8061e70 into main Aug 5, 2026
4 checks passed
@Cyclodex
Cyclodex deleted the claude/bexio-extension-issue-12-4ad9db branch August 5, 2026 14:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fixed columns for better orientation during booking

1 participant