Skip to content

OUT-3662 | Auto-archive: workspace setting, schema & UI#1197

Open
arpandhakal wants to merge 27 commits intomainfrom
OUT-3662
Open

OUT-3662 | Auto-archive: workspace setting, schema & UI#1197
arpandhakal wants to merge 27 commits intomainfrom
OUT-3662

Conversation

@arpandhakal
Copy link
Copy Markdown
Collaborator

Summary

Part 1 of OUT-3496. Sets up persistence, configuration UI, and the API for the per-workspace auto-archive threshold. The Trigger.dev cron that consumes this setting is the second subtask (OUT-3663).

  • New WorkspaceSetting table — workspaceId unique, autoArchiveAfterDays Int @default(0). 0 = off; allowed non-zero values are 7, 14, 30, 60, 90.
  • New /api/workspace-settings route — GET (find-or-create) and PATCH (zod-validated to {0, 7, 14, 30, 60, 90}). IU-only via Resource.WorkspaceSetting.
  • Listing page renamed /manage-templates/configure-tasks-app (308 redirect kept for the listing). Detail route stays at /manage-templates/{templateId} per the design intent. Visible "Manage templates" copy on buttons/breadcrumbs unchanged.
  • New AutoArchiveSection — toggle + conditional days dropdown, owns the app-bridge "Save settings" primary CTA (replaces the old "Create template" CTA, which is now an inline "+ Add template" trigger in the Templates section header).

Test plan

  • Run prisma migrate deploy against the target environment and confirm WorkspaceSettings exists with autoArchiveAfterDays defaulting to 0.
  • Visit the configure tasks app page (as an IU) — section "Auto-archive" renders with toggle off, description text reads "Automatically archive completed tasks after 30 days".
  • Toggle on → dropdown appears showing 30 days, "Save settings" CTA appears in the parent app header.
  • Click Save settings → PATCH /api/workspace-settings is sent with { autoArchiveAfterDays: 30 }, row upserted, no error.
  • Change dropdown to other allowed values (7, 14, 60, 90), save → value persists across reload.
  • Toggle off → dropdown hides, save → row stores 0, reload reflects off state.
  • Manually PATCH with an invalid value (e.g. 5) → API returns 400.
  • Visit a stale /manage-templates URL → redirected to /configure-tasks-app. Stale /manage-templates/{templateId} URLs continue to work (no redirect, route still owns them).
  • Open a template detail from the listing — navigation lands on /manage-templates/{templateId} and breadcrumbs link back to /configure-tasks-app.
  • Confirm Client (non-IU) tokens cannot read or write the workspace setting.

🤖 Generated with Claude Code

@linear-code
Copy link
Copy Markdown

linear-code Bot commented Apr 28, 2026

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 28, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
tasks-app Ready Ready Preview, Comment May 7, 2026 0:04am

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 28, 2026

Greptile Summary

This PR introduces a per-workspace auto-archive threshold setting: a new WorkspaceSettings DB table, a GET/PATCH API route with Zod validation and IU-only access, and a configuration UI (AutoArchiveSection) living on the renamed /configure-tasks-app page (with a 308 redirect from /manage-templates).

  • P1getWorkspaceSettings uses a non-atomic find-then-create pattern; two concurrent first-time requests will collide on the unique constraint and return a 500. Use upsert instead (see inline comment on workspaceSettings.service.ts).

Confidence Score: 4/5

Safe to merge after fixing the race condition in the find-then-create GET path.

One P1 race condition in the service layer (find-then-create instead of upsert) that can cause a 500 on concurrent first access, capped at 4. All other changes — the migration, DTO schema, policy wiring, UI components, and route redirects — are clean and correct.

src/app/api/workspace-settings/workspaceSettings.service.ts — non-atomic find-then-create needs to be replaced with upsert.

Important Files Changed

Filename Overview
src/app/api/workspace-settings/workspaceSettings.service.ts New service with correct policy checks and upsert for writes, but the GET find-then-create path has a race condition that can cause a 500 on concurrent first-access.
src/app/api/workspace-settings/workspaceSettings.controller.ts Clean controller wiring: noStore on GET, Zod parse on PATCH, authentication on both paths.
src/app/configure-tasks-app/page.tsx Page renamed and extended to fetch workspace settings; missing res.ok guard on getWorkspaceSetting fetch.
src/app/configure-tasks-app/ui/AutoArchiveSection.tsx New client component managing toggle + days dropdown state and wiring to usePrimaryCta; logic is straightforward and correct.
prisma/migrations/20260428100247_add_workspace_settings_table/migration.sql Adds WorkspaceSettings table with unique index on workspaceId; schema and constraints look correct.
src/types/dto/workspaceSettings.dto.ts Clean Zod schema validating autoArchiveAfterDays against the allowed enum; shared between API and frontend.
next.config.js Adds permanent 308 redirect from /manage-templates to /configure-tasks-app; intentionally scoped to exact match only.
src/app/api/core/services/policies.service.ts Correctly registers WorkspaceSetting in the client policy with an empty allowed-actions array, blocking client token access.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Page as /configure-tasks-app (RSC)
    participant API as /api/workspace-settings
    participant DB

    Browser->>Page: GET (initial load)
    Page->>API: GET — find-or-create
    API->>DB: findUnique by workspaceId
    alt Row exists
        DB-->>API: WorkspaceSetting row
    else First access (race-prone path)
        DB-->>API: null
        API->>DB: create workspaceId
        DB-->>API: new row
    end
    API-->>Page: autoArchiveAfterDays
    Page-->>Browser: Renders AutoArchiveSection

    Browser->>API: PATCH autoArchiveAfterDays (Save settings)
    API->>DB: upsert workspaceSetting
    DB-->>API: updated row
    API-->>Browser: 200 OK
Loading

Comments Outside Diff (2)

  1. src/app/api/workspace-settings/workspaceSettings.service.ts, line 140-145 (link)

    P1 Race condition in find-then-create

    findUnique + create is not atomic. Two concurrent GET requests for a workspace that has no setting yet will both see null, both attempt create, and the second will throw a Prisma unique-constraint error, surfacing as a 500. Use upsert instead to make the initial materialisation idempotent:

    const workspaceSetting = await this.db.workspaceSetting.upsert({
      where: { workspaceId },
      create: { workspaceId },
      update: {},
    })
    return workspaceSetting
  2. src/app/configure-tasks-app/page.tsx, line 218-221 (link)

    P2 Missing res.ok check on workspace settings fetch

    getWorkspaceSetting calls res.json() unconditionally. If the endpoint returns an error status (e.g. invalid token, server error), the error body is silently passed to AutoArchiveSection as initial state, or throws with an unhelpful parse error. Every other data-fetcher in this file throws on non-OK responses. Add an equivalent guard before calling res.json().

Reviews (1): Last reviewed commit: "feat(OUT-3662): add workspace auto-archi..." | Re-trigger Greptile

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 28, 2026

Deployment failed with the following error:

Deploying Serverless Functions to multiple regions is restricted to the Pro and Enterprise plans.

Learn More: https://vercel.link/multiple-function-regions

priosshrsth and others added 7 commits May 4, 2026 09:02
… page

- new WorkspaceSetting table (workspaceId unique, autoArchiveAfterDays Int default 0)
- /api/workspace-settings GET (find-or-create) + PATCH (zod-validated to {0,7,14,30,60,90}); IU-only
- listing page /manage-templates renamed to /configure-tasks-app with permanent redirect; details page /manage-templates/{id} stays put
- new Auto-archive section on the listing page (toggle + days dropdown) wired through the app-bridge "Save settings" CTA
- Templates section gets an inline "+ Add template" trigger; in-page "Create template" primary CTA removed (auto-archive section now owns the primary CTA)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- replace custom Stack trigger with GhostBtn + GrayAddMediumIcon for the "Add template" affordance (the white-stroke PlusIcon was invisible on a light surface; this matches the in-app tertiary-button pattern used by "Create subtask")
- restyle the days dropdown: light EDEDF0 border (matches Autocomplete), no MUI focus ring, soft drop-shadow on the menu paper, no hover highlight on items, only the selected item has a subtle gray[100] background
- disable open/close and chevron-rotate animations on the dropdown
- drop the "Automatically archive completed tasks after 30 days" subtext from the toggle row

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OUT-3663 | Auto-archive: daily cron job & activity log
priosshrsth
priosshrsth previously approved these changes May 7, 2026
@arpandhakal arpandhakal dismissed priosshrsth’s stale review May 7, 2026 06:35

The merge-base changed after approval.

priosshrsth
priosshrsth previously approved these changes May 7, 2026
@arpandhakal arpandhakal dismissed priosshrsth’s stale review May 7, 2026 08:22

The merge-base changed after approval.

arpandhakal and others added 6 commits May 7, 2026 17:46
…M company preview

When an internal user enters CRM preview mode for a company, also include
tasks assigned to clients of that company (TEAM TASKS) and IU tasks shared
with those clients (SHARED WITH TEAM). Previously only company-level tasks
and IU tasks shared with the company itself were visible.

The shared task/comment filter is now async and fetches the company's
clients via copilot.getCompanyClients only when the new IU-company-preview
branch fires; the result is memoized per request to avoid duplicate calls.
Other flows (client preview, CU portal, normal IU) skip the new branch
entirely, so they incur no extra I/O — only an await on a synchronously
resolved promise.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replaces react-dnd (HTML5 drag API + custom drag preview) with
@dnd-kit/core for smoother, pointer-tracked task dragging that aligns
with the App Library interaction. Drop business logic (redux update +
PATCH) is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Hoist DragOverlay/snapCenterToCursor imports to module top (ES modules
  hoist them anyway; the mid-file placement was misleading).
- Drop KeyboardSensor: without a column/section coordinateGetter it
  produced a non-functional drag affordance for keyboard users.
- Document the deliberate touchAction: 'manipulation' choice so the
  trade-off vs. dnd-kit's recommended 'none' is explicit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ies from limited-access IUs

Migrates Tasks.associations from jsonb[] to jsonb to enable Prisma's
array_contains operator, then filters tasks by association companyId
(not just assignee) for limited-access IUs across API, cascade SQL,
and realtime. Also fixes a pre-existing useFilter dedup bug that
caused disjoint-promoted subtasks to render twice.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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.

2 participants