Commit Graph

672 Commits

Author SHA1 Message Date
Philipinho d476d0b8ba feat(base): add editable page title above the full-page base view
Mirror the doc-page layout — the base name is editable via the same
TitleEditor used on document pages, sitting in a centered Container
above the table. The base table fills the remaining space below via
flex: 1, so the toolbar and grid stay anchored to the bottom of the
viewport instead of fighting the page header.
2026-04-27 05:02:13 +01:00
Philipinho 00e9d1d724 feat(base): add right-side scroll headroom on inline embed grid
Mirror the leftward padding with --embed-grid-pad-right = extendRight,
applied as padding-right on .grid. The user can now pan past the last
column into empty space — same shape as Notion's behavior. Standalone
full-page bases are unaffected (var unset → 0).
2026-04-27 04:44:47 +01:00
Philipinho 583d2d37c4 fix(base): pull AddRowButton out of grid so sticky-left actually sticks
As a child of .grid with grid-column: 1/-1, the button took the full
row width and the sticky-left offset got swallowed by the grid layout
— it scrolled along with the cells instead of anchoring to the left.

Move it to a sibling of .grid inside .gridWrapper, restyle as a
small inline-flex chip with width: max-content. Sticky-left now
keeps it pinned to page-content-left while the user scrolls
horizontally, matching the toolbar.

Add padding-bottom: 6px on .gridWrapper and a small vertical margin
on the button so the horizontal scrollbar no longer overlaps it.
2026-04-27 04:39:29 +01:00
Philipinho a992fa8a63 feat(base): keep New row button sticky during horizontal scroll
Add position: sticky + inset-inline-start to .addRowButton so the
'New row' affordance stays at the page-content edge while the user
scrolls horizontally — same shape as the toolbar staying put. The
sticky offset uses the existing --embed-grid-pad-left var: in embed
mode that's the leftward extension distance (so it sticks at
page-content-left); in standalone mode the var is 0 (sticks at the
grid's natural left edge, no visible effect when there's no scroll).

Add a body-color background and z-index: 4 so cells don't bleed
through during scroll.
2026-04-27 04:30:04 +01:00
Philipinho 7a119010c7 fix(base): suppress ProseMirror atom-node selection outline on inline embed 2026-04-27 04:23:16 +01:00
Philipinho 678c6f5f3f fix(base): drop outer panel border on inline embed grid
The .grid had a 1px outer border + small radius drawing a rounded
rectangle around the whole table. In standalone full-page mode that
reads as a panel and is fine. In an inline embed it looked like a
floating box, especially with the leftward padded area inside the
border.

Make the outer border and radius CSS vars; the embed wrapper sets
them to none/0 so inline databases get only cell-level gridlines,
matching Notion's document-style rendering. Standalone bases get
the default 1px panel frame as before.
2026-04-27 04:20:58 +01:00
Philipinho 030d3e878a feat(base): extend inline embed scroll viewport leftward, anchor first cell with grid padding
Bring back the leftward extension via negative margin-left so the
scroll viewport reaches AppShell.Main's left edge, then offset the
.grid with padding-left = extendLeft so the first cell still lines
up with page-content on load. The extended area becomes scrollable
empty space the user can pan into — same shape as Notion's inline
databases.

Done with one new CSS var (--embed-grid-pad-left) consumed by the
.grid in grid.module.css. Standalone full-page bases never set the
var, so it's a no-op there.
2026-04-27 04:13:34 +01:00
Philipinho a7105267ed fix(base): keep inline embed left edge at page-content, extend right only
Removing the leftward extension. With negative margin-left, the
grid grew leftward past page-content and the first column appeared
near the sidebar edge on load — wrong; Notion keeps the first column
aligned with page text. Drop margin-left and the extendLeft
calculation; only extend rightward toward AppShell.Main's right edge.

Leftward viewport extension (so frozen columns can lock at the
sidebar edge during scroll) becomes meaningful once we add frozen
columns. Deferred until that feature lands.
2026-04-27 04:05:18 +01:00
Philipinho 67f45ee61b fix(base): anchor inline embed extension to AppShell main, not first wider parent
The findWiderAncestor walk often stopped at a slightly-larger
intermediate (NodeViewWrapper, drag-handle wrapper, etc.) whose
left edge was almost the same as the wrapper's, so the leftward
extension came out as ~0 and the grid only grew to the right.

Use closest('main') instead — that's AppShell.Main, the layout
container whose bounds already reflect the navbar width and
sidebar collapse state. Both sides now extend to its edges.
2026-04-27 03:57:45 +01:00
Philipinho d73ac010af feat(base): use negative margin to extend inline grid past parent
The previous approach (position: relative + explicit width +
inset-inline-start) didn't physically grow the box in some flex
contexts, so the grid stayed at the parent's width. Switch to
negative margin-left / margin-right on the grid wrapper only —
with width: auto, the rendered width becomes parent + |margin|,
extending the box past the parent without any positioning hacks.

The toolbar keeps its natural parent-constrained width (no extension)
so it stays aligned with the page text above. Two CSS vars,
--embed-extend-l / --embed-extend-r, are computed on mount + on
ResizeObserver from the wrapper and the closest wider ancestor.
2026-04-27 03:51:21 +01:00
Philipinho 8132b171f4 fix(base): apply embed width-extension after base loads, not just on mount
useEffect ran once on mount with empty deps, but the wrapper div was
inside a conditional branch that only renders after the base query
resolves. Result: the ref was null when the effect ran, so nothing
extended. Move the wrapper outside the conditional so the ref is
always set, and re-run the effect when isLoading / isError / pageId
change so the extension applies once the table mounts.
2026-04-27 03:43:38 +01:00
Philipinho e0e87329f4 feat(base): inline embed extends grid + toolbar beyond page width
When a base is embedded inline in a doc page, measure the parent
container's available area and extend toolbar + grid sections to fill
it via CSS variables (--embed-width / --embed-shift / --embed-pad).
Inner content is re-padded so toolbar buttons and the first column
visually align with the page text, while the box itself reaches the
viewport edges for horizontal scroll headroom on wide databases.
Sticky inset-inline-start keeps the toolbar pinned to the page-content
edge during horizontal scroll. Standalone full-page bases are
unaffected (the embedded prop defaults to false).
2026-04-27 03:39:03 +01:00
Philipinho 8aabf86abb fix(base): make property type-picker popover scrollable with responsive width 2026-04-27 02:20:42 +01:00
Philipinho 7f8ed733a3 feat(base): inline base embed — node registration, slash command, and renderer view 2026-04-27 01:54:59 +01:00
Philipinho 69e7bd73f2 feat(base): render table icon in sidebar for is_base=true pages
- Add isBase to SpaceTreeNode type
- Forward isBase from IPage in buildTree utility
- In the sidebar Node renderer, show IconTable when node.data.isBase
  is true and no custom emoji icon is set
2026-04-27 01:50:05 +01:00
Philipinho 19821d3aeb feat(base): page renderer dispatches to base view when page.isBase
- Add isBase to page repo baseFields so it is always selected
- Add isBase to sidebar pages query select list
- Add isBase to client IPage type
- In PageContent, render <BaseTable pageId={page.id} /> when page.isBase
  is true instead of the TipTap editor path
2026-04-27 01:49:54 +01:00
Philipinho 060bd1048f refactor(base): rename /base/:baseId param to :pageId 2026-04-27 01:44:37 +01:00
Philipinho f56af5e6b4 refactor(base): rename baseId to pageId in client components 2026-04-27 01:44:11 +01:00
Philipinho 3e9f26a8dd refactor(base): rename baseId to pageId in client atoms, hooks, types 2026-04-27 01:42:54 +01:00
Philipinho 4510543510 refactor(base): rename baseId to pageId in client services and queries 2026-04-27 01:41:17 +01:00
Philipinho 137c02a10f feat(base): sticky table header so columns stay visible while scrolling
Pins the header row to the top of the gridWrapper scroll viewport, with
the pinned-left corner cell stacking above pinned body cells at the
top-left intersection. The trailing add-column button gets the same
sticky treatment so it doesn't drift away from the row.

Subtle bit: `position: sticky` confines an element to its containing
block. The previous subgrid `.headerRow` wrapper was 34px tall, so
sticky cells could only travel 34px before scrolling out with the
wrapper. Switching the wrapper to `display: contents` lets the cells
become direct grid children of `.grid` (full table height) so sticky
travel matches the scroll range. role="row" survives display:contents
in modern browsers.
2026-04-25 01:05:20 +01:00
Philipinho be79b7159c feat(base): warm row count query on base load, gated on user hydrate
Mounts useBaseRowsCountQuery alongside the rows query so the count is
fetched eagerly and the cache is warm by the time the toolbar consumes
it. Gating on `currentUser` (not just `base`) ensures the persisted
view-draft has hydrated from localStorage before the first count fires
— otherwise a post-refresh count races ahead of the user's saved
filter and ships without it.
2026-04-25 01:05:11 +01:00
Philipinho 72fc93dcc1 perf(base): batch page-cell resolution via microtask loader
Per-cell useResolvedPages([id]) calls each mounted with a unique
React Query key, so a grid with 20 page-typed cells fired 20 requests on
first paint. A shared loader now accumulates incoming ids within a
microtask, fires a single POST for the union, and fans the subset each
caller asked for back to them. Cells keep their own cache entry + null
handling; they just share the underlying network call.

Also renames /bases/pages/resolve → /bases/pages/expand — the old name
collided with other "resolve" semantics in the codebase.
2026-04-24 12:11:41 +01:00
Philipinho 89ee3714ac feat(base): add /bases/rows/count with estimate and capped-exact modes
Row-count display on a filtered view shouldn't force a full COUNT(*) on
every list fetch. New endpoint returns either an EXPLAIN-plan estimate
(default, ~1ms, no execution) or a LIMIT-capped exact count that short-
circuits to `{ capped: true }` once the match set passes EXACT_COUNT_CAP.
Clients call it in parallel with the rows query so the grid still paints
at its own pace.

- DTO + repo.countEstimate/countExact reusing the list predicate shape
- service picks the mode; controller mirrors the list Read ability check
- client hook keyed by filter/search/exact so a "show exact" toggle
  doesn't clobber the estimate cache
2026-04-24 12:11:29 +01:00
Philipinho b9d8bf948c fix(base): strip empty filter groups at the query boundary
The view-draft layer stores `{op: 'and', children: []}` as an explicit
"override baseline with no predicates" marker. That payload was leaking
into /bases/rows requests once local filter/sort drafts were enabled —
harmless server-side (buildWhere maps an empty group to TRUE) but it
destabilised the React Query key and cluttered request payloads. Normalise
empty groups to undefined at the hook level.
2026-04-24 12:11:02 +01:00
Philipinho dcff8d3c53 fix(base): mirror filter-clear fix for sorts so deleting the last sort sticks 2026-04-24 03:04:51 +01:00
Philipinho 2db43788d5 feat(base): auto-suffix fallback property names to avoid collisions 2026-04-24 02:54:03 +01:00
Philipinho 3bfdae7990 feat(base): insert palette items at cursor and refocus editor 2026-04-24 02:47:12 +01:00
Philipinho 464bd701ba feat(base): enforce unique property names per base 2026-04-24 02:34:38 +01:00
Philipinho 3962ccdc29 feat(base): tighten formula editor spacing and pill styling 2026-04-24 02:25:33 +01:00
Philipinho 7a254a9412 feat(base): redesign formula editor popover with polished header, pills, and accordion 2026-04-24 02:15:08 +01:00
Philipinho f99450f04a fix formula 2026-04-24 01:33:14 +01:00
Philipinho 3eb7c9b1d4 feat(base): render formula cells with error badge support 2026-04-24 00:35:57 +01:00
Philipinho 28fed815ba feat(base): add formula editor popover with live parse and palette 2026-04-24 00:35:26 +01:00
Philipinho 48d77a2b53 feat(base): add Formula entry to the property type picker 2026-04-24 00:33:38 +01:00
Philipinho 46f9292c05 feat(base): subscribe to formula WS events on client 2026-04-24 00:29:23 +01:00
Philipinho 493613e634 feat(base): add client formula type literals 2026-04-24 00:07:17 +01:00
Philipinho bf75cc9c74 label 2026-04-23 01:41:04 +01:00
Philipinho 5c8ce178e5 fix(base): stabilize choices reference so Add option row does not flicker 2026-04-21 12:04:36 +01:00
Philipinho cfb02766e2 refactor(base): simplify draft banner to inline Reset/Save controls 2026-04-20 23:12:51 +01:00
Philipinho ae595c51ed feat(base): mount draft banner and wire save-for-everyone flow 2026-04-20 23:00:32 +01:00
Philipinho 6c44354403 feat(base): add view draft banner component 2026-04-20 22:56:54 +01:00
Philipinho 184fa25d3e feat(base): route toolbar sort/filter changes through local draft 2026-04-20 22:54:25 +01:00
Philipinho 6740912adf feat(base): render table from effective (draft-or-baseline) view config 2026-04-20 22:50:28 +01:00
Philipinho f524243da1 refactor(base): accept baselineConfig option in useBaseTable 2026-04-20 22:47:00 +01:00
Philipinho d5093da863 feat(base): add useViewDraft hook for local filter/sort drafts 2026-04-20 22:44:05 +01:00
Philipinho 196afc21d4 feat(base): add BaseViewDraft type and view-draft atom family 2026-04-20 22:40:37 +01:00
Philipinho 9b5e3783dd feat(base): add Base subject to client-side space CASL enum 2026-04-20 22:38:15 +01:00
Philipinho 9ecf88511b page property 2026-04-20 21:27:29 +01:00
Philipinho eb0f37bfe5 update packages 2026-04-19 02:05:48 +01:00