* fix(editor): hide transclusion borders and reset spacing in read-only mode
* feat(share): add full width toggle for shared pages
* feat(share): support resizing sidebar on shared pages
* fix: auto redirect if there is only one SSO provider.
- fix tighten sso redirect
- fix share tree margin
* sync
* package overrides
* feat(tree): replace react-arborist with custom tree implementation
* feat(tree): keyboard arrow navigation between rows
* feat(emoji-picker): focus search input on open
* refactor(emoji): switch to @slidoapp/emoji-mart fork for accessibility
* feat(tree): Home/End and typeahead keyboard navigation
* feat(tree): roving tabindex and * to expand sibling subtrees
* feat(tree): Space activation and ARIA refinements
* fix(tree): move treeitem role to focusable row + aria-current
Adds SAML_DISABLE_REQUESTED_AUTHN_CONTEXT env var, passed through
to the SAML strategy's disableRequestedAuthnContext option.
Defaults to existing behavior (element sent). Set to true to omit
the element when the IdP authenticates the user with a method that
does not match (e.g. MFA, FIDO, passwordless), which would
otherwise cause AADSTS75011 with Microsoft Entra ID.
The sticky-headers refactor (cd8d1e0e) moved the header row's sticky
behavior up to .stickyBand and dropped the position rule from
.headerCell entirely — but the cell still has two absolutely-positioned
children that need it as their containing block:
- .resizeHandle (right: 0) — without a per-cell containing block, all
handles resolve up to .stickyBand and stack at the band's right
edge, so only one is visible.
- Popover.Target (inset: 0) — every header's popover anchor collapses
to the same band-relative box, so the property menu opens at one
fixed spot regardless of which header was clicked.
.headerCellPinned still establishes a containing block via position:
sticky, which is why the primary Title column kept working. Re-adding
position: relative on the base .headerCell fixes both the resize handle
and the property-menu popover for non-pinned columns; pinned cells are
unaffected because position: sticky later in the cascade still wins.
The inline-embed endpoint was a thin wrapper around baseService.create:
its only differences from /bases/create were that it derived the
spaceId/workspaceId from a parent page, gated permission via
pageAccessService.validateCanEdit on that parent, and seeded inline
defaults (Title + Text 1 + Text 2 + one row). All of that fits
naturally on /bases/create as a parentPageId branch.
- CreateBaseDto: spaceId is now optional. Either spaceId or
parentPageId must be supplied; the controller validates the
cross-field requirement and 400s otherwise.
- /bases/create: with parentPageId, derive workspaceId/spaceId from
the parent, validateCanEdit on the parent, apply inline defaults.
Without parentPageId, gate on space-level Create, Page (the
standalone path).
- InlineEmbedBaseDto + createInlineEmbed deleted.
- Slash command in the editor now POSTs /bases/create with
{ parentPageId } — the server picks up the inline branch.
BaseWsService.subscribe was the last surface that didn't go through
the page-permission system. It checked authorization with a bespoke
canReadBaseSpace(userId, spaceId) — which queried space membership
directly and accepted ANY space role — so a user with a per-base
restriction (revoked access via pagePermissionRepo) could still
stream live updates and presence for a base they couldn't otherwise
read.
Replace it with pageAccessService.validateCanView(base, user) — the
same gate the HTTP endpoints (info, list, rows query, etc.) and the
page collab WS already use. Bases are pages structurally (isBase=true),
so reusing the page validator keeps them on a single permission code
path.
Drops the now-unused SpaceMemberRepo / findHighestUserSpaceRole
imports; injects UserRepo + PageAccessService instead (both are
globally provided modules, no DI changes needed).
Bases are pages (isBase=true) and the casl rules already granted the
exact same Manage/Read level on the Base subject as on Page for every
space role (admin, writer, reader). The `Base` subject was therefore
pure duplication: any caller that needed to check base access either
went through pageAccessService (which uses Page internally) or did a
direct Page-equivalent ability.cannot(..., Base) check that produced
the same outcome as Page would have.
Drop SpaceCaslSubject.Base entirely — server enum, server union,
server factory rules, client enum, client union — and switch the two
remaining direct callers to Page:
- base.controller.ts `create` and list checks now use Page (matching
page.controller.ts's create/list).
- base-table.tsx's `canSave` now reads Page edit ability.
Net effect: one source of truth for "can this user view/edit/manage
content in this space," whether the content is a regular page or a
base. Existing role assignments behave identically; no migration
needed because permissions are computed per-request from the role,
not stored.
Switching to <Menu> in the previous fix made left-click on a tab
also open the menu — Mantine Menu auto-toggles via its Target's
click handler in controlled mode, which we don't want (left-click
should switch view, only right-click should open the context menu).
Switch back to <Popover> (no auto-toggle on Target click) and wire
outside-click / Escape close paths manually with a useEffect that's
active only while the menu is open. Capture-phase mousedown so we
run before grid-container's outside-click logic. Left-click on the
tab now calls onClick (switch view) and dismisses the menu in the
same gesture if it happens to be open.
The Rename / Delete-view popover (right-click on a view tab) wasn't
closing on outside click or Escape. The container was a <Popover>
with hand-rolled <UnstyledButton> menu items — closeOnClickOutside
and closeOnEscape on Mantine Popover only fire onClose when focus is
inside the dropdown, which never happens here because the popover
opens via a context-menu (focus stays on body) and there's no
trapFocus.
Switch to Mantine <Menu>, which is purpose-built for this pattern:
closeOnClickOutside / closeOnEscape work without focus being inside,
closeOnItemClick removes the manual setMenuOpened(false) wiring on
each item, and the keyboard arrow-key navigation is free.