Developer docs
Logs

Pending backend work

Frontend that shipped ahead of a backend field or endpoint — what's dark, and what unblocks it.

docs/PENDING_BACKEND.md

A living log of frontend work that shipped ahead of a backend field or endpoint it needs to be fully real. Every row here is UI that renders correctly and does not crash — it just has nothing (or fake data) behind it yet.

This is distinct from GAPS.md (backend infra/plumbing gaps with no frontend counterpart) and FEATURES.md (the broader roadmap). A row belongs here specifically when: the frontend is done, and the only thing standing between it and working is a named backend change.

Status: 🌑 dark — renders, but shows a fallback or mock data, nothing is broken · 🚧 not built — blocked entirely, no UI exists because there is nowhere to submit to yet · ✅ resolved — backend landed, frontend confirmed working (keep the row, don't delete it — dated history is the point).

Add a row the moment you ship frontend work that depends on a backend change that isn't there yet. Flip it to ✅ in the same commit that wires the real thing through, don't delete it.

Last updated: 2026-09-08 (same day, new row — Explore panel's multi-dimension filter gap).


Rows

Date addedFeatureUI elementFilesStatusBackend requirement
2026-09-08AvatarsPhoto upload during invite acceptance(not started — nowhere to submit to yet)🚧 not builtA self-upload endpoint (e.g. PATCH /users/me) so a user can set their own User.image. Nothing populates this column today except OAuth sign-in.
2026-09-08AvatarsHeader avatar (top-right profile menu)apps/web/src/components/user-menu.tsx🌑 dark — initials onlySession User contract has no image field. Add image: z.string().nullable() to userSchema (packages/contracts/src/index.ts) and populate it in the auth/session serializer. The DB column already exists (User.image).
2026-09-08AvatarsAssignment rows (My Work / AssignPanel)apps/web/src/components/assign-panel.tsx🌑 dark — initials onlypostAssignmentSchema (packages/contracts) has no image field at all. Needs the field added and the backing query updated to select it.
2026-09-08AvatarsApproval decision listapps/web/src/components/approval/review-path.tsx, apps/web/src/lib/approval-review.ts🌑 dark — initials only, but fully wiredGET /orgs/:orgId/users (apps/api/src/modules/authz/routes.ts) doesn't select image. Add image: true to the Prisma select and image: u.image to the response. One fix, benefits this row and the next one.
2026-09-08AvatarsApproval commentsapps/web/src/components/approval/review-comments.tsx, apps/web/src/lib/approval-review.ts🌑 dark — initials only, but fully wiredSame fix as the row above — same endpoint, same lookup.
2026-09-08Project onboarding formProduct type (SaaS/D2C), requiredapps/web/src/app/(app)/projects/page.tsx🌑 dark — collected, blocks submit, not sentAdd product_type: z.enum(["saas","d2c"]) to ProjectCreateInput/Project (packages/contracts/src/projects.ts). The exact line to add it on the frontend is commented in the submit handler.
2026-09-08Project onboarding formLogo URLapps/web/src/app/(app)/projects/page.tsx🌑 dark — collected, not sentAdd logo_url: z.string().nullable() to the same contract.
2026-09-08Project onboarding formSocial links (Instagram/LinkedIn/Facebook/X)apps/web/src/app/(app)/projects/page.tsx🌑 dark — collected, not sentAdd social_links (suggested: Partial<Record<SocialPlatform, string>>) to the same contract.
2026-09-08Global searchEntity results (posts, campaigns, everything searchable)apps/web/src/lib/global-search.ts🌑 dark — running entirely on a hardcoded mock arrayNo GET /search?q=… endpoint exists. The module already documents the swap: delete MOCK_SEARCH_RESULTS, point useGlobalSearch (components/search/search-palette.tsx) at the real hook — scoring/grouping/snippets are already written against SearchResult[] and don't change. Must be org-scoped server-side — the client must not be the thing filtering by tenant.
2026-09-08Project onboarding formProject role picked for an emailed inviteapps/web/src/app/(app)/projects/team-picker.tsx, apps/web/src/app/(app)/projects/page.tsx🌑 dark — role is chosen in the UI, never sentThe team picker shows one ProjectRole dropdown (Lead/Contributor/Agency/Viewer) for both a picked org member and an emailed invite, so the person filling the form only ever reasons about "their role on this project." For a picked member this is real (POST /projects/:id/members already accepts any ProjectRole). For an invite it isn't: invitationCreateSchema (packages/contracts/src/invitations.ts) has no field for an intended project role, only an org-wide role. Add project_role: projectRoleSchema.optional() to that schema, and have whatever creates the ProjectMember row on acceptance read it instead of hardcoding "contributor". Until then the frontend sends a fixed role: "member" (org role) on the invite and drops the picked project role on the floor — the accepted person always lands as "contributor" regardless of what was chosen.
2026-09-08Overview → Explore panelFilter row (Campaign / Placement / Device / … chips)apps/web/src/app/(app)/projects/[id]/analytics/_kit/explore-panel.tsx🌑 dark — chips render, values tick, chart never narrowsEach chip's dropdown is real (lazily fetches that one dimension's distinct values via a single-dimension usePivotReport call), and ticking values updates local state with a live count badge — but the chart request never receives them. pivotQuerySchema (packages/contracts/src/kpi-pivot.ts) only accepts a single platform string filter; there is no shape for "N dimensions, each restricted to a set of values" at once (the report itself already caps at MAX_PIVOT_DIMENSIONS = 2 breakdowns, which is a different limit — filters need to apply on TOP of whatever breakdown is chosen, not compete with it for one of the two slots). Needs: a filters param on pivotQuerySchema (e.g. dimension:value1|value2;dimension2:value3, mirroring how breakdowns/metrics already arrive as delimited strings), and buildPivotReport applying each as an AND-restriction on the underlying query before aggregating. Once that lands, wire filters from MetricExplorePanel's local state into both usePivotReport calls (current + previous-period) and drop the PREVIEW badge next to the row.

Not on this list, and why

  • Currency (project form) — fully real end to end already, was already on the contract.
  • Picking an existing org member for a project (project form) — fully real, including their chosen project role. Reuses the existing POST /projects/:id/members endpoint, which already accepts any ProjectRole.
  • Team list avatars — already fully working. GET /orgs/:orgId/members already returns image; the frontend just wasn't using it. Resolved without ever needing a row here.