The Activity Layer: workspace feed + home teaser - #310
Open
an1va wants to merge 2 commits into
Open
Conversation
Persists a new activity table (denormalized like notification, mirrored across SQLite/D1/Postgres) fed from the existing action routes: publish, comment create/resolve, proposal approve/request-changes, share, and a named viewer's first read of a version. Gated to org/link/public visibility so unlisted drafts and private docs never leak into the feed. Adds GET /v1/activity (cursor-paginated) and a new /activity page that day-groups and coalesces consecutive same-actor/same-artifact/same-kind rows into one story. Home gets one quiet TriageBar-style link to the latest story, not a whole module — disappears when nothing's happened. Follows the design in the "Activity Layer" concepts doc + mockups published earlier on Derive.
an1va
force-pushed
the
feat/activity-layer
branch
from
July 7, 2026 18:46
3633114 to
a634ea6
Compare
… don't hide it Found via a self-review before merge (8-angle code review, verified against the actual code and against a real ephemeral Postgres, not just sqlite): - deleteArtifact never deleted from the new `activity` table before deleting the artifact row, despite `activity.artifact_id` being a real FK. All THREE dialect implementations were missing it (packages/db/src/repos.ts for D1, sqlite.ts's own transactional override, and pg.ts) — on Postgres this threw a hard FK-violation and aborted every delete of an artifact that ever had activity recorded; on SQLite/D1 (no FK enforcement) it silently orphaned rows. - recordActivity's visibility gate only stopped NEW rows from being written for a private/unlisted/password artifact; nothing re-checked an artifact's CURRENT visibility on read. Downgrading a doc from org/link/public to private after the fact left its title and comment previews visible in the whole workspace's feed indefinitely, contradicting the code's own stated invariant. Fixed by making listActivity join against `artifact` and filter on its live visibility (packages/core/src/ports.ts's new FEED_VISIBLE_TIERS is the single shared source both the write-time gate and this read-time filter use, so the two can't drift independently). - Republishing with the `resolves` field (thread resolution bundled into a republish) never recorded a "resolve" activity, unlike the dedicated resolve endpoint doing the identical state change. Adds test coverage for the new /v1/activity route (auth gate, the visibility write/read gates, deletion, resolves-via-republish, cursor pagination) — there was none before. Verified against both embedded SQLite and a real ephemeral Postgres container (pnpm test:pg). Two other review findings were investigated and NOT changed, since they match pre-existing codebase conventions rather than being novel to this PR: activityActor returning null for the token principal mirrors actingUser/principalActor's existing behavior, and the `Number(limit) || 30` pattern on ?limit=0 is copied verbatim from the established GET /v1/artifacts handler.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
activitytable (denormalized likenotification, mirrored across SQLite/D1/Postgres) fed from 6 existing action routes: publish (×2 sites), comment create/resolve, proposal approve/request-changes, share, and a named viewer's first read of a version.org/link/publicvisibility — an unlisted draft or private doc never leaks its existence into the shared feed.GET /v1/activity(cursor-paginated, same keyset shape aslistArtifacts) and a new/activitypage: day-grouped, coalescing consecutive same-actor/same-artifact/same-kind rows into one story ("published 2 revisions of Q3 roadmap · v2 → v3", not two rows).Follows the design in "The Activity Layer: making Derive feel alive" and its mockups — this PR is stages 1+2 (spine + feed) plus the requested home link. Following-lens, proposals-in-the-bell, and the weekly digest are named follow-ups, not in scope here.
Walkthrough with real screenshots: https://derive.to/artifacts/the-activity-layer-shipped-walkthrough-iorihh89. (Images currently 404 on Chrome — unrelated platform bug found while testing this, fixed separately in #313.)
Test plan
pnpm run cigreen (biome + design-tokens + frontend + testids + api + schema + hyperdrive + filesize + anchor-client + deadcode)pnpm typecheckgreen across all 8 workspace packagespnpm test— 683 pre-existing tests pass, 0 regressions🤖 Generated with Claude Code
Self-review before merge
Ran an 8-angle review (correctness, removed-behavior, cross-file, reuse, simplification, efficiency, altitude, conventions) and verified the top findings directly against the code and a real ephemeral Postgres. Two real bugs fixed as a follow-up commit:
deleteArtifactnever cleaned up the newactivitytable. All three dialect implementations were missing it — on Postgres this threw a hard FK-violation and aborted the delete of any artifact that ever had activity recorded; on SQLite/D1 it silently orphaned rows. Fixed in all three (packages/db/src/repos.ts,sqlite.ts,pg.ts), verified against a real Postgres container.listActivityjoin againstartifactand filter on live visibility, sourced from one new shared constant (FEED_VISIBLE_TIERS) so the write and read gates can't drift apart.resolvesfield (bundling thread resolution into a republish) wasn't recording a "resolve" activity, unlike the dedicated resolve endpoint doing the same state change.GET /v1/activity(there was none): auth gate, both visibility gates, deletion, resolves-via-republish, cursor pagination — run against both embedded SQLite and a real Postgres container.Two other findings were investigated and intentionally left as-is:
activityActorreturning null for the token principal matchesactingUser/principalActor's existing behavior, and the?limit=0falsy-zero coercion is the same pattern already used verbatim in the establishedGET /v1/artifactshandler — fixing either in isolation would diverge from convention rather than fix a bug unique to this PR.