Skip to content

feat: add "Shared with me" sidebar section - #74

Merged
imaustink merged 3 commits into
mainfrom
feat/shared-with-me-sidebar
Oct 3, 2026
Merged

imaustink merged 3 commits into
mainfrom
feat/shared-with-me-sidebar

Conversation

@imaustink

Copy link
Copy Markdown
Owner

Problem

Folders and notes shared directly with a user were reachable only by URL — there was no way to discover them from the app:

  • Shared folders were never returned by GET /pages (its access filter only checks resource_type = 'page').
  • A note shared from inside a folder is returned, but its parentId points to a folder the recipient can't see, so the tree orphans it and it renders nowhere.

Change

A recipient-scoped path to surface shared items, plus a sidebar section for them.

API

  • ShareStore.ListSharedWithUser (pg + memstore) joins shares → pages for page/folder shares, projecting title, type, permission, and sharer (inner join drops shares whose resource was deleted).
  • GET /api/v1/shares/shared-with-me (not owner-gated — you can always see what's shared with you). New model.SharedItem.

Frontend

  • ApiShareRepository.sharedWithMe(), a shared.svelte.ts store loaded at mount, a SharedList.svelte component, and a "Shared with me" section in Sidebar.svelte (API mode only; folders link to the folder board, notes to the note; viewer-only items show a View badge).
  • Refreshes on tab focus / visibilitychange (coalesced) so shares granted while the tab was open appear without a full reload.

Tests

  • Go unit tests for the new store method (pg + memstore) and handler.
  • e2e/shared-sidebar.spec.ts covers: shared folder surfacing + board link + viewer badge, the orphaned-note case (absent from the tree, present in the section), empty state, revocation, and the editor-vs-viewer badge.
  • Full api E2E suite passes: 154 passed, 3 skipped, 0 failed (run on a local ferry/k8s cluster).

Note / follow-up (not in this PR)

While building this I found a pre-existing bug: the sidebar context-menu "Share" on a folder hardcodes resourceType="page" (TreeNodeItem.svelte:333), so folder shares from the sidebar send a page-type share and the backend 404s them. The working folder-share path is the folder board's Share button (resourceType: 'folder'), which this feature surfaces correctly. Happy to fix the sidebar path in a follow-up.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JBNnP4Gz2dqm5e2FZLDAeN

Folders and notes shared directly with a user were reachable only by
URL: shared folders were never returned by the page list, and a note
shared from inside a folder was orphaned under a parent the recipient
can't see, so it rendered nowhere in the sidebar.

Add a recipient-scoped way to surface them:

- API: ShareStore.ListSharedWithUser (pg + memstore) joins shares to the
  pages table for page/folder shares, projecting title/type/permission/
  sharer; exposed as GET /shares/shared-with-me. New model.SharedItem.
- Frontend: ApiShareRepository.sharedWithMe(), a shared.svelte.ts store
  loaded at mount, a SharedList sidebar component, and a "Shared with me"
  section in Sidebar.svelte (API mode, folders → folder board, notes →
  note; viewer badge). Refreshes on tab focus/visibility so new shares
  appear without a reload.

Tests: Go unit tests for the store/handler; e2e/shared-sidebar.spec.ts
covers folder surfacing + board link, the orphaned-note case, empty
state, revocation, and the editor-vs-viewer badge. Full api E2E suite
passes (154 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBNnP4Gz2dqm5e2FZLDAeN
@k5s-bot

k5s-bot Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

🤖 Starting work on this now.

@k5s-bot

k5s-bot Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

🤖 Watch live or take over the session here: https://claude.ai/code/session_01Lck5TZ8vR7FrF5sjggH9m8

@k5s-bot k5s-bot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid feature — the recipient-scoped endpoint is correctly not owner-gated but still requireSessionAuth-gated, the inner join / memstore both drop deleted resources, and coverage (Go unit + e2e) is thorough. Build and the touched Go packages pass locally. Two things worth a look before merge; the folder-context-menu bug you already documented is a fine follow-up and out of scope here.

Comment thread src/routes/+layout.svelte
Comment thread api/internal/store/store.go
- +layout.svelte: load sharedStore off the critical Promise.all so a
  non-auth failure fetching /shares/shared-with-me can't trip the app-wide
  loadError path and block notes/tasks from rendering. handleAuthError
  still runs the redirect for a real UnauthorizedError before .catch
  swallows the re-throw.
- Sidebar.svelte: dedupe the section against the page tree. A note shared
  at the owner's root is returned by GET /pages and renders at the
  recipient's tree root, so it was listed twice; now only items not
  reachable in the tree (folder shares and folder-nested notes) show in
  "Shared with me".
- e2e: add a test asserting a root-shared note renders once (tree only,
  absent from the section). Full api suite passes (155 passed).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBNnP4Gz2dqm5e2FZLDAeN
@imaustink

Copy link
Copy Markdown
Owner Author

Both review points addressed in ec5bce4:

  1. +layout.svelte critical-path load — sharedStore.load() is now off the fatal Promise.all: void sharedStore.load().catch(() => {}). A non-auth failure fetching /shares/shared-with-me no longer trips the app-wide loadError; handleAuthError still runs the redirect for a real UnauthorizedError before .catch swallows the re-throw.

  2. Root-shared note double-listing — the section is now deduped against the page tree in Sidebar.svelte. Only items not reachable in the recipient's tree are shown: folder shares (never in GET /pages) and folder-nested notes (orphaned under an invisible parent). A note shared at the owner's root renders in the main tree and is filtered out of the section. Added an e2e test asserting a root-shared note renders exactly once (tree only, section absent).

Full api E2E suite re-run on the ferry/k8s cluster after a frontend image rebuild: 155 passed, 3 skipped, 0 failed.

The folder-context-menu resourceType bug remains a documented out-of-scope follow-up.

The frontend CI job's `pnpm audit --prod` step failed on new devalue
advisories (uneval/CPU amplification, __proto__ bypass, sparse-array
DoS) affecting versions up to 5.9.2, pulled in transitively via svelte.
The existing override only covered <=5.8.0, so 5.9.2 slipped through.

Broaden it to `devalue@<5.9.3` → `>=5.9.3 <6` (resolves to 5.9.4). The
<6 cap keeps it within svelte's `^5.8.1` range and avoids 6.x's
Node >=22.17 engine requirement, which would fail engine-strict installs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JBNnP4Gz2dqm5e2FZLDAeN
@imaustink
imaustink merged commit 7f24a5d into main Oct 3, 2026
9 checks passed
@imaustink
imaustink deleted the feat/shared-with-me-sidebar branch October 3, 2026 14:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant