Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
✅ Deploy Preview for fresco-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Major v4.0.0 release update introducing a new form system, schema v8 support, preview mode, multi-user capability, and a revamped drag-and-drop/dashboard UI, alongside significant refactors and infrastructure/config updates.
Changes:
- Added preview mode infrastructure (API routes, DB fields, auth toggle, preview interview flow).
- Introduced new form field system (TanStack Form contexts/hooks + many new field components).
- Reworked drag-and-drop (new
lib/dndstore/hooks/utils, plus refactors in interviewer components).
Reviewed changes
Copilot reviewed 293 out of 804 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| lib/interviewer/components/Panels.js | Formatting simplification for panel wrapper |
| lib/interviewer/components/Panel.tsx | New TSX panel component with collapse/minimize/highlight logic |
| lib/interviewer/components/Panel.js | Removed legacy Panel implementation |
| lib/interviewer/components/NodeBin.tsx | New NodeBin using new ~/lib/dnd drop target hook |
| lib/interviewer/components/NodeBin.js | Removed legacy NodeBin drag/drop HOC implementation |
| lib/interviewer/components/Node.tsx | New memoized Node wrapper + MotionNode |
| lib/interviewer/components/Node.js | Removed legacy Node implementation |
| lib/interviewer/components/Navigation.tsx | Navigation refactor (props-driven disable state + passphrase prompt) |
| lib/interviewer/components/MultiNodeBucket.js | Removed legacy bucket component |
| lib/interviewer/components/Edge.js | Updated edge color selector wiring |
| lib/interviewer/components/DialogManager.js | Redux action import refactor |
| lib/interviewer/components/CollapsablePrompts.js | Added support for rendering children in prompts container |
| lib/interviewer/components/CategoricalItem.js | Removed legacy categorical item implementation |
| lib/interviewer/components/Canvas/NodeLayout.js | Updated updateNode API usage |
| lib/interviewer/components/Canvas/NarrativeEdgeLayout.js | Minor formatting cleanup |
| lib/interviewer/components/Canvas/LayoutNode.js | Layout node rendering tweaks (size + import ordering) |
| lib/interviewer/components/Canvas/EdgeLayout.js | Selector refactor for codebook lookup |
| lib/interviewer/components/Canvas/ConvexHulls.js | Selector import consolidation |
| lib/interviewer/components/Canvas/Canvas.js | Formatting cleanup |
| lib/interviewer/components/BackgroundImage.js | Formatting cleanup |
| lib/interviewer/behaviours/scrollable.js | Removed legacy scrollable HOC |
| lib/interviewer/behaviours/DragAndDrop/useDropMonitor.js | Removed legacy drop monitor hook |
| lib/interviewer/behaviours/DragAndDrop/store.js | Store middleware updated for unserializable drag/drop values |
| lib/interviewer/behaviours/DragAndDrop/reducer.js | Action type constants changed from Symbols to strings |
| lib/interviewer/behaviours/DragAndDrop/index.js | Export surface adjusted |
| lib/interviewer/behaviours/DragAndDrop/MonitorDropTarget.js | Removed legacy monitor HOC |
| lib/interviewer/behaviours/DragAndDrop/MonitorDragSource.js | Removed legacy monitor HOC |
| lib/interviewer/behaviours/DragAndDrop/Monitor.js | Removed legacy monitor base |
| lib/interviewer/behaviours/DragAndDrop/DropTarget.js | Import cleanup |
| lib/interviewer/behaviours/DragAndDrop/DropObstacle.js | Formatting + minor simplifications |
| lib/interviewer/behaviours/DragAndDrop/DragSource.js | Defaults added for handler props |
| lib/interviewer/behaviours/DragAndDrop/DragPreview.js | Removed eslint disable header (left blank line) |
| lib/interviewer/behaviours/AssetMetaProvider.js | Selector import/path refactor |
| lib/form/utils/scrollToFirstError.ts | Added helper to scroll to first form error |
| lib/form/utils/formContexts.ts | Added TanStack Form contexts |
| lib/form/types.ts | Added core form typing layer |
| lib/form/hooks/useTanStackForm.ts | Added TanStack Form hook wrapper |
| lib/form/components/fields/utils/options.js | Added option normalization helpers |
| lib/form/components/fields/VisualAnalogScale.js | Added VAS wrapper field |
| lib/form/components/fields/ToggleButton.tsx | Added ToggleButton field |
| lib/form/components/fields/Toggle.tsx | Added Toggle field |
| lib/form/components/fields/TextArea.tsx | Added TextArea field |
| lib/form/components/fields/Slider/index.js | Added Slider barrel export |
| lib/form/components/fields/Slider/Track.js | Added slider Track component |
| lib/form/components/fields/Slider/Tick.js | Added slider Tick component |
| lib/form/components/fields/Slider/Field.js | Added Slider field wrapper |
| lib/form/components/fields/Search.tsx | Added Search field wrapper |
| lib/form/components/fields/RelativeDatePicker.js | Added relative date picker wrapper |
| lib/form/components/fields/Radio.tsx | Added Radio field |
| lib/form/components/fields/MarkdownLabel.js | Added markdown label wrapper |
| lib/form/components/fields/Markdown.js | Added markdown renderer with sanitize/raw support |
| lib/form/components/fields/LikertScale.js | Added Likert scale wrapper |
| lib/form/components/fields/FieldSkeleton.tsx | Added skeleton for loading states |
| lib/form/components/fields/DatePicker/index.js | Added DatePicker barrel export |
| lib/form/components/fields/DatePicker/helpers.js | Added DatePicker helper utilities |
| lib/form/components/fields/DatePicker/Panels.js | Added DatePicker animated panels container |
| lib/form/components/fields/DatePicker/Panel.js | Added DatePicker animated panel |
| lib/form/components/fields/DatePicker/Field.js | Added DatePicker field wrapper |
| lib/form/components/fields/DatePicker/DatePicker/helpers.js | Added DatePicker internal helpers |
| lib/form/components/fields/DatePicker/DatePicker/Years.js | Added years range supplier |
| lib/form/components/fields/DatePicker/DatePicker/Months.js | Added months range supplier |
| lib/form/components/fields/DatePicker/DatePicker/Days.js | Added days range supplier |
| lib/form/components/fields/DatePicker/DatePicker/DatePickerContext.js | Added DatePicker context |
| lib/form/components/fields/DatePicker/DatePicker/Date.js | Added DatePicker date selector helper |
| lib/form/components/fields/Checkbox.js | Added checkbox field component (redux-form style props) |
| lib/form/components/fields/Boolean.js | Added boolean field component |
| lib/dnd/utils.ts | Added DnD hit detection + raf throttle utilities |
| lib/dnd/types.ts | Added core DnD types |
| lib/dnd/index.ts | Added DnD public exports |
| lib/dnd/tests/setup.ts | Added test environment setup for DnD |
| lib/dnd/DndStoreProvider.tsx | Added Zustand-based DnD store provider + drag preview portal |
| lib/dialogs/useDialog.stories.tsx | Added story for useDialog |
| lib/dialogs/Dialog.stories.tsx | Added dialog stories |
| lib/dialogs/ControlledDialog.tsx | Added controlled dialog variant |
| lib/dialogs/ControlledDialog.stories.tsx | Added controlled dialog stories |
| lib/db/schema.prisma | Added preview + pending protocol fields, ApiToken model, asset value |
| lib/db/migrations/20251204222357_add_protocol_is_pending/migration.sql | Migration: add Protocol.isPending |
| lib/db/migrations/20251118073416_add_preview_mode_and_api_tokens/migration.sql | Migration: preview mode + ApiToken + index |
| lib/db/migrations/20250426201139_add_protocol_experiments/migration.sql | Migration: add Protocol.experiments |
| lib/db/migrations/20250122184616_add_asset_value/migration.sql | Migration: add Asset.value + join table pk changes |
| lib/cache.ts | Cache tags refactor + multi-tag revalidation + deployment keying |
| knip.json | Knip config updates + Playwright wiring |
| jsconfig.json | Added JS compilerOptions config file |
| hooks/usePortal.ts | Made usePortal client-safe for SSR + delayed mount |
| hooks/useCanvas.ts | Formatting cleanup |
| hooks/use-data-table.tsx | Updated data table types import path |
| fresco.config.ts | Added schema v8 + preview min architect version |
| env.js | Added PREVIEW_MODE env var |
| docker-compose.prod.yml | Updated prod compose env + ports + volumes |
| docker-compose.dev.yml | YAML formatting cleanup |
| components/ui/tooltip.tsx | Tailwind class ordering tweaks |
| components/ui/toaster.tsx | Tailwind class ordering tweaks |
| components/ui/toast.tsx | Fixed Heading import path |
| components/ui/table.tsx | Table container styling/layout changes |
| components/ui/switch.tsx | Tailwind class ordering tweaks |
| components/ui/separator.tsx | Quote/style normalization |
| components/ui/select.tsx | Tailwind class ordering tweaks |
| components/ui/progress.tsx | Tailwind class ordering tweaks |
| components/ui/popover.tsx | Quote/style normalization |
| components/ui/dialog.tsx | Fixed typography import paths |
| components/ui/checkbox.tsx | Tailwind class ordering tweaks |
| components/ui/card.tsx | Fixed Heading import path + class changes |
| components/ui/badge.tsx | Fixed export formatting |
| components/ui/Label.tsx | Fixed Heading import path |
| components/ui/Input.tsx | Tailwind class ordering tweaks |
| components/ui/CloseButton.tsx | Made onClick optional + class ordering |
| components/ui/Button.tsx | Updated tableHeader variant styles |
| components/ui/AlertDialog.tsx | Fixed typography import paths |
| components/ui/Alert.tsx | Fixed typography import paths |
| components/typography/Heading.tsx | Minor expression parenthesization cleanup |
| components/layout/SettingsSection.tsx | Updated Heading import path |
| components/layout/ResponsiveContainer.tsx | Added new content base size |
| components/interview/ActionButton.tsx | Added interview action button component |
| components/interview/ActionButton.stories.tsx | Added ActionButton story |
| components/data-table/data-table.tsx | Updated types import path + layout tweaks |
| components/data-table/data-table-toolbar.tsx | Updated types import path + layout tweaks |
| components/data-table/data-table-skeleton.tsx | Layout tweaks |
| components/data-table/data-table-pagination.tsx | Updated pageSizes import path |
| components/data-table/data-table-floating-bar.tsx | Minor typing + class ordering |
| components/data-table/data-table-faceted-filter.tsx | Updated Option import path + class ordering |
| components/data-table/advanced/data-table-advanced-toolbar.tsx | Updated types import path + wrapper removal |
| components/data-table/advanced/data-table-advanced-filter.tsx | Updated types import path |
| components/VersionSection.tsx | Typography import path updates + class ordering |
| components/Providers/index.tsx | Added global Providers wrapper (Motion/Radix/Dialog/Toaster) |
| components/Providers/RadixDirectionProvider.tsx | Added Radix direction provider |
| components/ProtocolImport/JobReducer.ts | Added “Migrating protocol” import status |
| components/ProtocolImport/JobCard.tsx | Typography import updates + class ordering |
| components/PreviewModeAuthSwitch.tsx | Added switch for preview auth requirement |
| components/ErrorReportNotifier.tsx | Class ordering tweaks |
| components/ErrorDetails.tsx | Import ordering + class ordering tweaks |
| components/DynamicLucideIcon.tsx | Added CMS-safe lucide icon resolver |
| components/DataTable/types.ts | Added new activity types (API token + preview mode) |
| components/DataTable/DataTable.tsx | Removed redundant wrapper border |
| components/DataTable/ColumnHeader.tsx | UI tweaks for sortable headers |
| components/CloseButton.tsx | Added new CloseButton wrapper component |
| components/BackgroundBlobs/Canvas.tsx | Tailwind conversion for sizing |
| components/BackgroundBlobs/BackgroundBlobs.tsx | Inlined RNG helpers (removed import) |
| app/not-found.tsx | Typography import path updates + class ordering |
| app/layout.tsx | Added Providers + ResponsiveContainer path change |
| app/global-error.tsx | Typography import path updates + ResponsiveContainer path change |
| app/error.tsx | Typography import path updates + ResponsiveContainer path change |
| app/dashboard/settings/loading.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/settings/_components/UpdateUploadThingToken.tsx | Switched to generic UpdateSettingsValue API |
| app/dashboard/settings/_components/UpdateInstallationId.tsx | Switched to generic UpdateSettingsValue API |
| app/dashboard/protocols/page.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/protocols/loading.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/protocols/_components/DeleteProtocolsDialog.tsx | Updated Protocol type import + improved warning copy |
| app/dashboard/participants/page.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/participants/loading.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/participants/_components/ParticipantModal.tsx | Typography import path updates |
| app/dashboard/participants/_components/ImportCSVModal.tsx | Typography import path updates + error message change |
| app/dashboard/participants/_components/ExportParticipants/ImportExportSection.tsx | Typography + ResponsiveContainer import updates |
| app/dashboard/participants/_components/ExportParticipants/GenerateParticipantURLsButton.tsx | Updated types to table client exports |
| app/dashboard/participants/_components/ExportParticipants/ExportParticipants.tsx | Added SuperJSON parsing for participants payload |
| app/dashboard/participants/_components/ExportParticipants/ExportCSVParticipantURLs.tsx | Updated types to table client exports |
| app/dashboard/participants/_components/DropzoneField.tsx | Typography import path updates |
| app/dashboard/participants/_components/DeleteParticipantsDialog.tsx | Alert variant set + improved warning copy |
| app/dashboard/page.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/loading.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/interviews/page.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/interviews/loading.tsx | Typography + ResponsiveContainer import path updates |
| app/dashboard/interviews/_components/GenerateInterviewURLs.tsx | Added SuperJSON parsing for protocols payload |
| app/dashboard/interviews/_components/ExportOptionsView.tsx | Typography import path updates |
| app/dashboard/interviews/_components/ExportInterviewsDialog.tsx | Added SuperJSON parsing for prepared export payloads |
| app/dashboard/interviews/_components/ExportCSVInterviewURLs.tsx | Updated protocol/interview types |
| app/dashboard/_components/UploadThingModal.tsx | Typography import path updates |
| app/dashboard/_components/UpdateSettingsValue.tsx | Simplified API: now uses setAppSetting by key |
| app/dashboard/_components/SummaryStatistics/SummaryStatistics.tsx | ResponsiveContainer import path updates |
| app/dashboard/_components/SummaryStatistics/StatCard.tsx | Typography import path updates |
| app/dashboard/_components/RecruitmentTestSectionServer.tsx | Typography import path updates |
| app/dashboard/_components/RecruitmentTestSection.tsx | Added SuperJSON parsing for promises |
| app/dashboard/_components/ProtocolsTable/ProtocolsTableClient.tsx | Added SuperJSON parsing + exported ProtocolWithInterviews type |
| app/dashboard/_components/ProtocolsTable/ProtocolsTable.tsx | Removed unstable_noStore usage |
| app/dashboard/_components/ProtocolsTable/Columns.tsx | Updated imports + ProtocolWithInterviews type source |
| app/dashboard/_components/ProtocolsTable/ActionsDropdown.tsx | Updated ProtocolWithInterviews type source |
| app/dashboard/_components/ProtocolUploader.tsx | Class ordering tweaks |
| app/dashboard/_components/ParticipantsTable/ParticipantsTableClient.tsx | Added SuperJSON parsing + exported ParticipantWithInterviews type |
| app/dashboard/_components/ParticipantsTable/GenerateParticipantURLButton.tsx | Updated protocol types + typography import path |
| app/dashboard/_components/ParticipantsTable/Columns.tsx | Updated protocol/participant type sources + typography import path |
| app/dashboard/_components/ParticipantsTable/ActionsDropdown.tsx | Updated participant type source |
| app/dashboard/_components/NavigationBar.tsx | Typography import path update + Image priority |
| app/dashboard/_components/InterviewsTable/InterviewsTable.tsx | Added SuperJSON parsing for interviews payload |
| app/dashboard/_components/ActivityFeed/utils.ts | Updated ActivityType type import path |
| app/dashboard/_components/ActivityFeed/SearchParams.ts | Updated filter/sort type import path |
| app/dashboard/_components/ActivityFeed/ColumnDefinition.tsx | Updated data-table types import path + minor cell render tweak |
| app/api/uploadthing/route.ts | Use derived base URL for callback, tolerate missing token |
| app/api/test/clear-cache/route.ts | Added test-only cache invalidation endpoint |
| app/api/preview/types.ts | Added preview message exchange types |
| app/api/preview/helpers.ts | Added preview mode auth helper + CORS response helpers |
| app/(interview)/preview/layout.tsx | Added preview layout with small-screen overlay |
| app/(interview)/preview/[protocolId]/route.ts | Added preview protocol gate + redirect handler |
| app/(interview)/preview/[protocolId]/interview/page.tsx | Added preview interview page (in-memory interview construction) |
| app/(interview)/layout.tsx | Added shared interview root layout with metadata + styles import |
| app/(interview)/interview/layout.tsx | Simplified interview layout (moved overlay out) |
| app/(interview)/interview/finished/page.tsx | Typography import path updates + class ordering |
| app/(interview)/interview/_components/SmallScreenOverlay.tsx | Typography import path updates |
| app/(interview)/interview/_components/ServerSync.tsx | Removed legacy client-side sync component |
| app/(interview)/interview/_components/InterviewShell.tsx | Switched to SuperJSON payload + store factory + DnD provider |
| app/(interview)/interview/[interviewId]/sync/route.ts | Added POST route to persist interview state |
| app/(interview)/interview/[interviewId]/page.tsx | Switched to SuperJSON payload + updated finished interview guard |
| app/(interview)/interview/[interviewId]/layout.tsx | Added per-session layout with small-screen overlay |
| app/(blobs)/layout.tsx | Class ordering tweaks |
| app/(blobs)/expired/page.tsx | Removed runtime redirect check + styling tweak |
| app/(blobs)/(setup)/setup/page.tsx | Class ordering tweaks |
| app/(blobs)/(setup)/layout.tsx | Forced dynamic rendering for DB-backed expiry check |
| app/(blobs)/(setup)/_components/SignInForm.tsx | Typography import path updates |
| app/(blobs)/(setup)/_components/Sidebar.tsx | Typography import path updates |
| app/(blobs)/(setup)/_components/OnboardSteps/UploadProtocol.tsx | Typography import path updates |
| app/(blobs)/(setup)/_components/OnboardSteps/ManageParticipants.tsx | Typography import path updates |
| app/(blobs)/(setup)/_components/OnboardSteps/Documentation.tsx | Typography import path updates |
| app/(blobs)/(setup)/_components/OnboardSteps/CreateAccount.tsx | Typography import path updates |
| app/(blobs)/(setup)/_components/OnboardSteps/ConnectUploadThing.tsx | Typography import path updates |
| actions/uploadThing.ts | Updated UploadThing helper import path |
| actions/reset.ts | Batch tag revalidation + UploadThing helper import path update |
| actions/protocols.ts | Protocol insert refactor (schema/typing changes) |
| actions/appSettings.ts | App setting schema refactor + centralized getStringValue |
| actions/activityFeed.ts | Updated activity types import path |
| Dockerfile | Added build arg + NODE_OPTIONS change + prisma CLI install step |
| CLAUDE.md | Documentation whitespace + “Debugging” tips section |
| .vscode/settings.json | Editor defaults + Tailwind regex tweaks |
| .vscode/launch.json | JSON formatting cleanup |
| .vscode/extensions.json | JSON formatting cleanup |
| .storybook/preview.tsx | Switched to nextjs-vite preview + Providers decorator |
| .storybook/preview.ts | Removed old preview config |
| .storybook/main.ts | Switched Storybook framework to @storybook/nextjs-vite |
| .prettierrc | Added tabWidth/useTabs options |
| .github/workflows/docker-build-pr.yml | Input to disable image optimization + renamed image |
| .github/workflows/chromatic.yml | Added Chromatic CI workflow |
| .github/dependabot.yml | Quote/style normalization |
| .github/workflows/update-snapshots.yml | Removed snapshot update workflow |
| .github/workflows/playwright.yml | Removed Playwright CI workflow |
| .eslintrc.cjs | Removed legacy ESLint config |
| .serena/memories/* | Removed Serena memory docs/ignores |
Comments suppressed due to low confidence (1)
actions/protocols.ts:1
- The function signature indicates
protocolInsertSchemais the source of truth, but the runtime validation (protocolInsertSchema.parse(input)) was removed. This allows malformed/untrusted input to reach Prisma writes (and can cause runtime exceptions or invalid DB state). Re-introduceprotocolInsertSchema.parse(input)(orsafeParsewith a clear error) and destructure from the parsed result.
'use server';
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 293 out of 802 changed files in this pull request and generated 12 comments.
Comments suppressed due to low confidence (1)
app/dashboard/participants/_components/ImportCSVModal.tsx:1
ZodError.messageis often a generic/verbose summary and may be less actionable than showing a specific issue. Consider usinge.errors[0]?.message(or formattinge.errors) to surface the most relevant validation problem to the user.
'use client';
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
🎭 Playwright E2E Test Report❌ Tests failed. View the full report here: |
🎭 Playwright E2E Test Report❌ Tests failed. View the full report here: |
The export progress toast never appeared: it was created with timeout: Infinity. Base UI gates its auto-dismiss timer on duration > 0, and Infinity > 0 is true, so it scheduled setTimeout(closeToast, Infinity) — which the browser coerces to ~0ms (verified: fires at 0.6ms, same as setTimeout(0)). The toast was dismissed immediately, and every subsequent update() hit an already-closing toast (Base UI ignores those), so no progress ever rendered. Use timeout: 0 (no auto-dismiss), matching the persistent-toast pattern in useProtocolImport.
The runner stage copied the full ~1.7GB dev node_modules over the standalone output and ran `pnpm add --force prisma` with no cache mount, baking pnpm`\s 3.6GB store + 965MB cache into the image. Instead, keep the standalone`\s own traced node_modules and install only the deps the startup scripts need (prisma CLI, tsx, and the packages prisma.config.ts / env.js / the .ts scripts import) into a temp dir, then merge that closure into /app/node_modules so it resolves from both /app and /app/scripts. Next bundles the app`\s runtime deps into the .next chunks, so the standalone node_modules lacks them — hence the merge rather than an isolated scripts/node_modules. migrate-and-start.sh calls the binaries by path. Verified the image boots: prisma generate, migrations, and server all start. 8.86GB -> 822MB.
…etion Two fixes to the export download stream: - Coalesce progress events: a large export emits one progress event per item (3000 interviews -> ~20k events, 17k+ during file writing), flooding the SSE stream and re-rendering the client toast hundreds of times per second. Forward a progress event only when its integer percentage changes; always forward stage events. ~20k events -> ~200. - Fix the live status-refresh race: exportTime + revalidateTag now run BEFORE the `complete` event is written, so by the time the client receives it and calls router.refresh(), the updated status and invalidated cache are already in place. Previously they ran after closing the stream, so the refresh could race them and show stale data until the next navigation.
The export status was not updating in the table when an export finished, even though exportTime committed correctly (verified in the DB). Cause: the export runs in a route handler, which can only safeRevalidateTag (stale-while- revalidate), so the client router.refresh() re-fetched the STALE cached list. Add a revalidateInterviewsAfterExport server action that safeUpdateTags getInterviews + activityFeed (read-your-own-writes: EXPIRES the cache so the next read is fresh); the client calls it, then router.refresh() to re-fetch. Both are required: refresh alone reads the route's stale-while-revalidate value, and the action alone is not enough because it runs in the detached streaming IIFE where Next's post-action auto-revalidation (which refreshes direct-handler actions like deleteInterviews) does not fire. The prior commit moved the exportTime commit before the `complete` event, so it is in place.
…resolution
Rewrites getInterviews to accept InterviewsSearchParams and return a paged
result { rows, pageCount, totalCount } with WHERE/ORDER BY/LIMIT/OFFSET
injected from the shared builders. Adds getInterviewFilterOptions (cached,
supplies protocol names + node/edge types from codebooks) and
getInterviewIdsMatching (uncached, powers bulk-export criteria). Adds the
resolveInterviewIds server action wrapping getInterviewIdsMatching.
…ria-based bulk actions
…k blobs getProtocols included full interviews (every network JSON blob, ~37MB at 3k interviews), exceeding the cache item size limit so the query was uncacheable and re-ran on every dashboard render. Consumers only read interview count + export status, so select just exportTime.
The runtime-dep npm install had no cache and deleted /root/.npm each build, so it re-downloaded (and occasionally stalled minutes) on every rebuild. Mount a BuildKit cache at /root/.npm and stop wiping it — subsequent rebuilds reuse the cached tarballs. The cache lives in BuildKit, not the image layer.
…tecture Storage architecture: direct export streaming, env-configurable storage, private asset bucket
Bump all Storybook packages from 10.4.1 to 10.4.4, the newest release that satisfies the repo's minimum release-age supply-chain policy (10.4.5 was published the same day and is gated by the policy). No changes to minimumReleaseAgeExclude — the protection is left intact. https://claude.ai/code/session_012Tpgamsx795fRAL8Dpo4bX
Bump 29 minor/patch dependencies to their newest releases, all within the repo's minimum release-age supply-chain policy (pnpm resolved policy-compliant versions; minimumReleaseAgeExclude left untouched). Includes next/react 16.2.9/19.2.7, vite 8.0.16, vitest 4.1.8, tailwindcss 4.3.1, chromatic 17.4.1, posthog, aws-sdk, and others. Verified: typecheck, lint, 300 unit tests, and storybook build all pass. https://claude.ai/code/session_012Tpgamsx795fRAL8Dpo4bX
Bump sharp from 0.34.5 to 0.35.1 (0.x minor / breaking under semver), within the minimum release-age policy. sharp is used by Next.js image optimization; not imported directly in app code. Verified: native binary loads, image processing works, typecheck passes. https://claude.ai/code/session_012Tpgamsx795fRAL8Dpo4bX
…inci-7lw4a3 chore(deps): upgrade Storybook and update dependencies
…ets (#792) Replaces the single-invocation ticket/zip-stream export with client-orchestrated batching: the browser fetches bounded per-batch streams (with retry/abort), assembles one zip with fflate, then commits via a server action. Fixes the 0-byte 'Export complete' on large exports (~3000) on serverless, with no object storage. Includes review hardening: file-framing invariants, genuine-error surfacing over sibling aborts, a batch-size cap, accurate exported-count logging, and a11y.
* security: harden auth, storage, export and infra for the 4.0.0 release Addresses findings from the pre-release security & correctness audit. - auth: enforce the "app not configured" invariant inside the signup actions (not only the setup page) and require auth for completeSetup; equalize login response timing to prevent username enumeration; await failed login-attempt records so the rate limiter counts them. - access control: scope /api/assets/[key] to DB-referenced keys with strict key validation so it cannot sign a GET for an arbitrary object; serve uploaded assets with a validated content-type, X-Content-Type-Options: nosniff, and attachment disposition for script-capable types (stored XSS). - export: neutralize CSV/formula injection in the network-exporters CSV serializer (pnpm patch) and the activity-feed export; validate the unauthenticated onboard participant identifier; clamp export concurrency. - data integrity: stop trusting the client-supplied interview lastUpdated (Prisma @updatedat sets it server-side); return a generic 400 from sync. - secrets: store API tokens as a SHA-256 hash; document configuring storage via environment variables to keep reversible secrets out of the database. - headers: add nosniff / X-Frame-Options / HSTS / Referrer-Policy, with no-referrer on /interview and /onboard so capability-bearing URLs do not leak; stop logging the interview id. - ssrf: reject private, loopback, and link-local S3 endpoints. - infra: override the transitive `effect` off the vulnerable version; require MinIO credentials (no minioadmin default); drop the host bind-mount that exposed .env into the app container; trim /api/health disclosure; re-enable build-time type checking. - docs: SECURITY.md security model + data-at-rest guidance; .env.example storage variables; fix stale CLAUDE.md paths (lib/db, @codaco/network-exporters). Adds unit tests for the signup gate, API-token hashing, CSV escaping, the SSRF URL check, and the hardened asset route. * review: bound export concurrency in schema, drop unneeded CSV escaping - export: move the concurrency bound into exportInterviewsSchema so an out-of-range value is a validation error (400) instead of a silent clamp; remove the clamp from the batch route. - activity feed: remove CSV formula-escaping and delete escapeCsvFormula — the exported cells (timestamp/type/message) are always prefixed with literal text and never start with a participant-controlled character. - SECURITY.md: clarify that authenticated admins can create accounts from the dashboard settings; only the unauthenticated setup signup is gated to before configuration. * review: keep MAX_EXPORT_CONCURRENCY local to the schema module * review: remove network-exporters CSV patch (fix to be made upstream)
* fix(dashboard): show finished interviews as 100% progress Interview progress on the interviews table was computed purely as currentStep / stageCount, but a finished interview's currentStep never reaches stageCount: the FinishSession screen is appended in-memory by @codaco/interview and isn't part of the stored protocol stages, and the finish flow records only finishTime (no sync advances currentStep). This left completed interviews at (stageCount-1)/stageCount, e.g. 92%. Derive completion from finishTime (the canonical completion signal): a finished interview is 100%. This also fixes existing finished interviews with no migration, and makes the "Complete"/"In Progress" progress filter presets behave correctly. Apply via a shared computeInterviewProgress helper (display) and a PROGRESS_SQL expression (filter + sort) so all three progress computations stay consistent. * fix(dashboard): align interview progress denominator with @codaco/interview The dashboard derived progress from currentStep / stageCount, but @codaco/interview indexes currentStep against [...protocolStages, finishStage] — so the true step total is stageCount + 1, not stageCount. Dividing by stageCount drifted every in-progress percentage away from what the participant actually saw in the interview. Use currentStep / (stageCount + 1) for in-progress interviews so the dashboard scale matches the package's. finishTime still overrides to 100%, which is the only reliable completion signal (the finish-screen step is not guaranteed to sync before the finish redirect).
#803) * chore(deps): update @codaco packages and migrate collected categorical data Bump all @codaco packages to latest, including @codaco/protocol-utilities 1.x -> 2.0.0. Adapt the generateNetwork call sites in the test-interview generator to the new options-object signature (seed moved into options). @codaco/interview 1.1.0 stores categorical attribute values as arrays of selected option values, dropping the scalar-handling fallbacks. Add a one-time, idempotent migration that wraps scalar categorical values in collected interview networks into single-element arrays, resolving categorical variables from the protocol codebook. Wired into the build:platform migration step after the v8 protocol migration. Closes #795 * fix(migrations): run data migrations atomically in a transaction Wrap the protocol-v8 and interview-categorical data migrations in a single interactive transaction so a failure during setup-database rolls them all back. A partially-migrated database is dangerous on a failed 3.x -> 4.x upgrade: the previous Fresco version keeps serving and would read half-converted protocols/networks written for the new interview module. All-or-nothing keeps the old version working on the old data. * fix(migrations): reconstruct asset manifest for v8 protocol validation protocol-validation 11.7.0 added strict validation that a NameGeneratorRoster dataSource (and Geospatial asset references) resolve to an entry in the protocol's assetManifest. migrateProtocolsToV8 reconstructed the protocol from only name/schemaVersion/stages/codebook, so the manifest was absent and migration failed for any protocol with a roster: Roster dataSource "..." does not reference an asset in the manifest. Fresco stores assets in a separate table, so rebuild the assetManifest from the protocol's linked Asset rows and include it in the document passed to migrateProtocol. The manifest is excluded from the protocol hash, so this does not change computed hashes. Verified against the real validator with a roster protocol. * refactor(interview): import contract utils from @codaco/interview/contract createInitialNetwork and isValidAssetType were imported from the @codaco/interview package root, whose single bundle evaluates React createContext at module load and so crashes the RSC production build when pulled into server code (server action / server component). Import them instead from the new server-safe @codaco/interview/contract entry (complexdatacollective/network-canvas-monorepo#705), and drop the temporary local reimplementations so the factory stays single-sourced in the package. NOTE: blocked on publishing @codaco/interview@1.2.0 with the ./contract export. Until then the build/install is red (the subpath does not resolve against 1.1.0). Once published, refresh the lockfile (pnpm install) to go green — no code change. * chore(deps): lock @codaco/interview to 1.2.0 (server-safe ./contract export) @codaco/interview@1.2.0 is published with the ./contract entry. Refresh the lockfile so the @codaco/interview/contract imports resolve, unblocking the RSC production build.
Significant release!
Waiting on: