Repository: mfts/papermark Branch: main Commit: fd7d01771a5c Files: 1468 Total size: 6.2 MB Directory structure: gitextract_qhe605p_/ ├── .agents/ │ └── skills/ │ ├── frontend-design/ │ │ ├── LICENSE.txt │ │ └── SKILL.md │ ├── gh-cli/ │ │ └── SKILL.md │ ├── postgres/ │ │ ├── SKILL.md │ │ └── references/ │ │ ├── backup-recovery.md │ │ ├── index-optimization.md │ │ ├── indexing.md │ │ ├── memory-management-ops.md │ │ ├── monitoring.md │ │ ├── mvcc-transactions.md │ │ ├── mvcc-vacuum.md │ │ ├── optimization-checklist.md │ │ ├── partitioning.md │ │ ├── process-architecture.md │ │ ├── ps-cli-api-insights.md │ │ ├── ps-cli-commands.md │ │ ├── ps-connection-pooling.md │ │ ├── ps-connections.md │ │ ├── ps-extensions.md │ │ ├── ps-insights.md │ │ ├── query-patterns.md │ │ ├── replication.md │ │ ├── schema-design.md │ │ ├── storage-layout.md │ │ └── wal-operations.md │ ├── vercel-react-best-practices/ │ │ ├── AGENTS.md │ │ ├── SKILL.md │ │ └── rules/ │ │ ├── advanced-event-handler-refs.md │ │ ├── advanced-init-once.md │ │ ├── advanced-use-latest.md │ │ ├── async-api-routes.md │ │ ├── async-defer-await.md │ │ ├── async-dependencies.md │ │ ├── async-parallel.md │ │ ├── async-suspense-boundaries.md │ │ ├── bundle-barrel-imports.md │ │ ├── bundle-conditional.md │ │ ├── bundle-defer-third-party.md │ │ ├── bundle-dynamic-imports.md │ │ ├── bundle-preload.md │ │ ├── client-event-listeners.md │ │ ├── client-localstorage-schema.md │ │ ├── client-passive-event-listeners.md │ │ ├── client-swr-dedup.md │ │ ├── js-batch-dom-css.md │ │ ├── js-cache-function-results.md │ │ ├── js-cache-property-access.md │ │ ├── js-cache-storage.md │ │ ├── js-combine-iterations.md │ │ ├── js-early-exit.md │ │ ├── js-hoist-regexp.md │ │ ├── js-index-maps.md │ │ ├── js-length-check-first.md │ │ ├── js-min-max-loop.md │ │ ├── js-set-map-lookups.md │ │ ├── js-tosorted-immutable.md │ │ ├── rendering-activity.md │ │ ├── rendering-animate-svg-wrapper.md │ │ ├── rendering-conditional-render.md │ │ ├── rendering-content-visibility.md │ │ ├── rendering-hoist-jsx.md │ │ ├── rendering-hydration-no-flicker.md │ │ ├── rendering-hydration-suppress-warning.md │ │ ├── rendering-svg-precision.md │ │ ├── rendering-usetransition-loading.md │ │ ├── rerender-defer-reads.md │ │ ├── rerender-dependencies.md │ │ ├── rerender-derived-state-no-effect.md │ │ ├── rerender-derived-state.md │ │ ├── rerender-functional-setstate.md │ │ ├── rerender-lazy-state-init.md │ │ ├── rerender-memo-with-default-value.md │ │ ├── rerender-memo.md │ │ ├── rerender-move-effect-to-event.md │ │ ├── rerender-simple-expression-in-memo.md │ │ ├── rerender-transitions.md │ │ ├── rerender-use-ref-transient-values.md │ │ ├── server-after-nonblocking.md │ │ ├── server-auth-actions.md │ │ ├── server-cache-lru.md │ │ ├── server-cache-react.md │ │ ├── server-dedup-props.md │ │ ├── server-parallel-fetching.md │ │ └── server-serialization.md │ └── web-design-guidelines/ │ └── SKILL.md ├── .cursor/ │ └── rules/ │ ├── rule-claude-opus.mdc │ └── rule-trigger-typescript.mdc ├── .cursorignore ├── .eslintrc.json ├── .github/ │ ├── CODEOWNERS │ ├── ISSUE_TEMPLATE/ │ │ └── oss-gg-hack-template.yml │ └── workflows/ │ └── cla.yml ├── .gitignore ├── .prettierignore ├── CLA.md ├── LICENSE ├── Pipfile ├── README.md ├── SECURITY.md ├── app/ │ ├── (auth)/ │ │ ├── auth/ │ │ │ ├── confirm-email-change/ │ │ │ │ └── [token]/ │ │ │ │ ├── page-client.tsx │ │ │ │ ├── page.tsx │ │ │ │ └── utils.ts │ │ │ ├── email/ │ │ │ │ └── [[...params]]/ │ │ │ │ ├── page-client.tsx │ │ │ │ └── page.tsx │ │ │ └── saml/ │ │ │ ├── page-client.tsx │ │ │ └── page.tsx │ │ ├── layout.tsx │ │ ├── login/ │ │ │ ├── page-client.tsx │ │ │ └── page.tsx │ │ ├── register/ │ │ │ ├── page-client.tsx │ │ │ └── page.tsx │ │ └── verify/ │ │ ├── invitation/ │ │ │ ├── AcceptInvitationButton.tsx │ │ │ ├── InvitationStatusContent.tsx │ │ │ ├── page.tsx │ │ │ └── status/ │ │ │ └── ClientRedirect.tsx │ │ └── page.tsx │ ├── (ee)/ │ │ ├── LICENSE.md │ │ ├── README.md │ │ └── api/ │ │ ├── ai/ │ │ │ ├── chat/ │ │ │ │ ├── [chatId]/ │ │ │ │ │ ├── messages/ │ │ │ │ │ │ └── route.ts │ │ │ │ │ └── route.ts │ │ │ │ └── route.ts │ │ │ └── store/ │ │ │ ├── runs/ │ │ │ │ └── [runId]/ │ │ │ │ └── route.ts │ │ │ └── teams/ │ │ │ └── [teamId]/ │ │ │ ├── datarooms/ │ │ │ │ └── [dataroomId]/ │ │ │ │ └── route.ts │ │ │ ├── documents/ │ │ │ │ └── [documentId]/ │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ ├── auth/ │ │ │ └── saml/ │ │ │ ├── authorize/ │ │ │ │ └── route.ts │ │ │ ├── callback/ │ │ │ │ └── route.ts │ │ │ ├── token/ │ │ │ │ └── route.ts │ │ │ ├── userinfo/ │ │ │ │ └── route.ts │ │ │ └── verify/ │ │ │ └── route.ts │ │ ├── faqs/ │ │ │ └── route.ts │ │ ├── links/ │ │ │ └── [id]/ │ │ │ └── upload/ │ │ │ └── route.ts │ │ ├── scim/ │ │ │ └── v2.0/ │ │ │ └── [...directory]/ │ │ │ └── route.ts │ │ ├── teams/ │ │ │ └── [teamId]/ │ │ │ ├── directory-sync/ │ │ │ │ └── route.ts │ │ │ └── saml/ │ │ │ └── route.ts │ │ ├── workflow-entry/ │ │ │ ├── domains/ │ │ │ │ └── [...domainSlug]/ │ │ │ │ └── route.ts │ │ │ └── link/ │ │ │ └── [entryLinkId]/ │ │ │ ├── access/ │ │ │ │ └── route.ts │ │ │ └── verify/ │ │ │ └── route.ts │ │ └── workflows/ │ │ ├── [workflowId]/ │ │ │ ├── executions/ │ │ │ │ └── route.ts │ │ │ ├── route.ts │ │ │ └── steps/ │ │ │ ├── [stepId]/ │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ └── route.ts │ ├── api/ │ │ ├── auth/ │ │ │ └── verify-code/ │ │ │ └── route.ts │ │ ├── cron/ │ │ │ ├── dataroom-digest/ │ │ │ │ ├── daily/ │ │ │ │ │ └── route.ts │ │ │ │ └── weekly/ │ │ │ │ └── route.ts │ │ │ ├── domains/ │ │ │ │ ├── route.ts │ │ │ │ └── utils.ts │ │ │ ├── welcome-user/ │ │ │ │ └── route.ts │ │ │ └── year-in-review/ │ │ │ └── route.ts │ │ ├── csp-report/ │ │ │ └── route.ts │ │ ├── feature-flags/ │ │ │ └── route.ts │ │ ├── help/ │ │ │ └── route.ts │ │ ├── integrations/ │ │ │ └── slack/ │ │ │ └── oauth/ │ │ │ ├── authorize/ │ │ │ │ └── route.ts │ │ │ └── callback/ │ │ │ └── route.ts │ │ ├── og/ │ │ │ ├── route.tsx │ │ │ └── yir/ │ │ │ └── route.tsx │ │ ├── verify/ │ │ │ └── login-link/ │ │ │ └── route.ts │ │ ├── views/ │ │ │ ├── pages/ │ │ │ │ └── route.ts │ │ │ └── route.ts │ │ ├── views-dataroom/ │ │ │ └── route.ts │ │ └── webhooks/ │ │ └── callback/ │ │ └── route.ts │ ├── layout.tsx │ └── robots.txt ├── components/ │ ├── EmailForm.tsx │ ├── Skeleton.tsx │ ├── account/ │ │ ├── account-header.tsx │ │ ├── update-subscription.tsx │ │ └── upload-avatar.tsx │ ├── agreements/ │ │ └── agreement-card.tsx │ ├── ai-elements/ │ │ ├── conversation.tsx │ │ ├── message.tsx │ │ ├── prompt-input.tsx │ │ └── shimmer.tsx │ ├── analytics/ │ │ ├── analytics-card.tsx │ │ ├── dashboard-views-chart.tsx │ │ ├── documents-table.tsx │ │ ├── links-table.tsx │ │ ├── time-range-select.tsx │ │ ├── views-table.tsx │ │ └── visitors-table.tsx │ ├── billing/ │ │ ├── add-seat-modal.tsx │ │ ├── plan-badge.tsx │ │ ├── pro-annual-banner.tsx │ │ ├── pro-banner.tsx │ │ ├── upgrade-plan-container.tsx │ │ ├── upgrade-plan-modal-old.tsx │ │ ├── upgrade-plan-modal-with-discount.tsx │ │ ├── upgrade-plan-modal.tsx │ │ └── yearly-upgrade-banner.tsx │ ├── blur-image.tsx │ ├── charts/ │ │ ├── bar-chart-tooltip.tsx │ │ ├── bar-chart.tsx │ │ └── utils.ts │ ├── conversations/ │ │ └── index.tsx │ ├── datarooms/ │ │ ├── actions/ │ │ │ ├── download-dataroom.tsx │ │ │ ├── generate-index-button.tsx │ │ │ ├── generate-index-dialog.tsx │ │ │ ├── rebuild-index-button.tsx │ │ │ └── remove-document-modal.tsx │ │ ├── add-dataroom-modal.tsx │ │ ├── add-viewer-modal.tsx │ │ ├── analytics/ │ │ │ ├── analytics-overview.tsx │ │ │ ├── document-analytics-tree.tsx │ │ │ └── mock-analytics-table.tsx │ │ ├── dataroom-breadcrumb.tsx │ │ ├── dataroom-card.tsx │ │ ├── dataroom-document-card.tsx │ │ ├── dataroom-header.tsx │ │ ├── dataroom-items-list.tsx │ │ ├── dataroom-navigation.tsx │ │ ├── dataroom-trial-modal.tsx │ │ ├── download-progress-modal.tsx │ │ ├── edit-dataroom-document-modal.tsx │ │ ├── empty-dataroom.tsx │ │ ├── export-visits-modal.tsx │ │ ├── folders/ │ │ │ ├── index.tsx │ │ │ ├── selection-tree.tsx │ │ │ ├── sidebar-tree.tsx │ │ │ ├── utils.ts │ │ │ └── view-tree.tsx │ │ ├── groups/ │ │ │ ├── add-group-modal.tsx │ │ │ ├── add-member-modal.tsx │ │ │ ├── delete-group/ │ │ │ │ ├── delete-group-modal.tsx │ │ │ │ └── index.tsx │ │ │ ├── group-card-placeholder.tsx │ │ │ ├── group-card.tsx │ │ │ ├── group-header.tsx │ │ │ ├── group-member-table.tsx │ │ │ ├── group-navigation.tsx │ │ │ ├── group-permissions.tsx │ │ │ └── set-unified-permissions-modal.tsx │ │ ├── move-dataroom-folder-modal.tsx │ │ ├── settings/ │ │ │ ├── bulk-download-settings.tsx │ │ │ ├── dataroom-tag-section.tsx │ │ │ ├── delete-dataroooom/ │ │ │ │ ├── delete-dataroom-modal.tsx │ │ │ │ └── index.tsx │ │ │ ├── duplicate-dataroom.tsx │ │ │ ├── introduction-settings.tsx │ │ │ ├── notification-settings.tsx │ │ │ ├── permission-settings.tsx │ │ │ └── settings-tabs.tsx │ │ ├── sortable/ │ │ │ ├── sortable-item.tsx │ │ │ └── sortable-list.tsx │ │ └── stats-card.tsx │ ├── document-upload.tsx │ ├── documents/ │ │ ├── actions/ │ │ │ ├── delete-documents-modal.tsx │ │ │ └── delete-folder-modal.tsx │ │ ├── add-document-modal.tsx │ │ ├── add-document-to-dataroom-modal.tsx │ │ ├── add-folder-to-dataroom-modal.tsx │ │ ├── alert.tsx │ │ ├── annotations/ │ │ │ ├── annotation-form.tsx │ │ │ └── annotation-sheet.tsx │ │ ├── breadcrumb.tsx │ │ ├── delete-folder-modal.tsx │ │ ├── document-card.tsx │ │ ├── document-header.tsx │ │ ├── document-preview-button.tsx │ │ ├── document-preview-modal.tsx │ │ ├── document-stats-placeholder.tsx │ │ ├── documents-list.tsx │ │ ├── drag-and-drop/ │ │ │ ├── draggable-item.tsx │ │ │ └── droppable-folder.tsx │ │ ├── empty-document.tsx │ │ ├── export-visits-modal.tsx │ │ ├── file-process-status-bar.tsx │ │ ├── filters/ │ │ │ └── sort-button.tsx │ │ ├── folder-card.tsx │ │ ├── hidden-document-card.tsx │ │ ├── hidden-documents-list.tsx │ │ ├── hidden-folder-card.tsx │ │ ├── link-document-indicator.tsx │ │ ├── loading-document.tsx │ │ ├── move-folder-modal.tsx │ │ ├── notion-accessibility-indicator.tsx │ │ ├── pagination.tsx │ │ ├── preview-viewers/ │ │ │ ├── index.ts │ │ │ ├── preview-excel-viewer.tsx │ │ │ ├── preview-image-viewer.tsx │ │ │ ├── preview-pages-viewer.tsx │ │ │ └── preview-viewer.tsx │ │ ├── stats-card.tsx │ │ ├── stats-chart-dummy.tsx │ │ ├── stats-chart-skeleton.tsx │ │ ├── stats-chart.tsx │ │ ├── stats-element.tsx │ │ ├── stats.tsx │ │ ├── video-analytics.tsx │ │ ├── video-chart-placeholder.tsx │ │ └── video-stats-placeholder.tsx │ ├── domains/ │ │ ├── add-domain-modal.tsx │ │ ├── delete-domain-modal.tsx │ │ ├── domain-card.tsx │ │ ├── domain-configuration.tsx │ │ └── use-domain-status.ts │ ├── emails/ │ │ ├── custom-domain-setup.tsx │ │ ├── data-rooms-information.tsx │ │ ├── dataroom-digest-notification.tsx │ │ ├── dataroom-notification.tsx │ │ ├── dataroom-trial-24h.tsx │ │ ├── dataroom-trial-end.tsx │ │ ├── dataroom-trial-welcome.tsx │ │ ├── dataroom-upload-notification.tsx │ │ ├── deleted-domain.tsx │ │ ├── download-ready.tsx │ │ ├── email-updated.tsx │ │ ├── export-ready.tsx │ │ ├── hundred-views-congrats.tsx │ │ ├── installed-integration-notification.tsx │ │ ├── invalid-domain.tsx │ │ ├── onboarding-1.tsx │ │ ├── onboarding-2.tsx │ │ ├── onboarding-3.tsx │ │ ├── onboarding-4.tsx │ │ ├── otp-verification.tsx │ │ ├── shared/ │ │ │ └── footer.tsx │ │ ├── slack-integration.tsx │ │ ├── team-invitation.tsx │ │ ├── thousand-views-congrats.tsx │ │ ├── upgrade-one-month-checkin.tsx │ │ ├── upgrade-personal-welcome.tsx │ │ ├── upgrade-plan.tsx │ │ ├── upgrade-six-month-checkin.tsx │ │ ├── verification-email-change.tsx │ │ ├── verification-link.tsx │ │ ├── viewed-dataroom-paused.tsx │ │ ├── viewed-dataroom.tsx │ │ ├── viewed-document-paused.tsx │ │ ├── viewed-document.tsx │ │ ├── welcome.tsx │ │ └── year-in-review-papermark.tsx │ ├── folders/ │ │ ├── add-folder-modal.tsx │ │ ├── edit-folder-modal.tsx │ │ ├── folder-color-picker.tsx │ │ └── folder-icon-picker.tsx │ ├── gtm-component.tsx │ ├── hooks/ │ │ ├── use-optimistic-update.ts │ │ └── useLastUsed.tsx │ ├── layouts/ │ │ ├── app.tsx │ │ ├── blocking-modal.tsx │ │ ├── breadcrumb.tsx │ │ └── trial-banner.tsx │ ├── links/ │ │ ├── delete-link-modal.tsx │ │ ├── embed-code-modal.tsx │ │ ├── link-active-controls.tsx │ │ ├── link-sheet/ │ │ │ ├── agreement-panel/ │ │ │ │ └── index.tsx │ │ │ ├── agreement-section.tsx │ │ │ ├── ai-agents-section.tsx │ │ │ ├── allow-download-section.tsx │ │ │ ├── allow-list-section.tsx │ │ │ ├── allow-notification-section.tsx │ │ │ ├── conversation-section.tsx │ │ │ ├── custom-fields-panel/ │ │ │ │ ├── custom-field.tsx │ │ │ │ └── index.tsx │ │ │ ├── custom-fields-section.tsx │ │ │ ├── dataroom-link-sheet.tsx │ │ │ ├── deny-list-section.tsx │ │ │ ├── domain-section.tsx │ │ │ ├── email-authentication-section.tsx │ │ │ ├── email-protection-section.tsx │ │ │ ├── expiration-section.tsx │ │ │ ├── expirationIn-section.tsx │ │ │ ├── feedback-section.tsx │ │ │ ├── index-file-section.tsx │ │ │ ├── index.tsx │ │ │ ├── link-item.tsx │ │ │ ├── link-options.tsx │ │ │ ├── link-success-sheet.tsx │ │ │ ├── og-section.tsx │ │ │ ├── password-section.tsx │ │ │ ├── permissions-sheet.tsx │ │ │ ├── pro-banner-section.tsx │ │ │ ├── question-section.tsx │ │ │ ├── screenshot-protection-section.tsx │ │ │ ├── tags/ │ │ │ │ ├── tag-badge.tsx │ │ │ │ ├── tag-details.tsx │ │ │ │ └── tag-section.tsx │ │ │ ├── upload-section/ │ │ │ │ └── index.tsx │ │ │ ├── watermark-panel/ │ │ │ │ └── index.tsx │ │ │ ├── watermark-section.tsx │ │ │ └── welcome-message-section.tsx │ │ ├── links-table.tsx │ │ ├── links-visitors.tsx │ │ └── preview-button.tsx │ ├── navigation-menu.tsx │ ├── profile-menu.tsx │ ├── profile-search-trigger.tsx │ ├── providers/ │ │ └── posthog-provider.tsx │ ├── search-box.tsx │ ├── search-command.tsx │ ├── settings/ │ │ ├── delete-team-modal.tsx │ │ ├── delete-team.tsx │ │ ├── global-block-list-form.tsx │ │ ├── ignored-domains-form.tsx │ │ ├── og-preview.tsx │ │ ├── settings-header.tsx │ │ ├── slack-settings-skeleton.tsx │ │ ├── survey-settings.tsx │ │ └── timezone-selector.tsx │ ├── shared/ │ │ ├── dealflow-popup.tsx │ │ ├── icons/ │ │ │ ├── advanced-sheet.tsx │ │ │ ├── alert-circle.tsx │ │ │ ├── arrow-up.tsx │ │ │ ├── badge-check.tsx │ │ │ ├── bar-chart.tsx │ │ │ ├── check-cirlce-2.tsx │ │ │ ├── check.tsx │ │ │ ├── chevron-down.tsx │ │ │ ├── chevron-right.tsx │ │ │ ├── chevron-up.tsx │ │ │ ├── circle.tsx │ │ │ ├── cloud-download-off.tsx │ │ │ ├── copy-right.tsx │ │ │ ├── copy.tsx │ │ │ ├── external-link.tsx │ │ │ ├── eye-off.tsx │ │ │ ├── eye.tsx │ │ │ ├── facebook.tsx │ │ │ ├── file-up.tsx │ │ │ ├── files/ │ │ │ │ ├── cad.tsx │ │ │ │ ├── docs.tsx │ │ │ │ ├── image.tsx │ │ │ │ ├── map.tsx │ │ │ │ ├── notion.tsx │ │ │ │ ├── pdf.tsx │ │ │ │ ├── sheet.tsx │ │ │ │ ├── slides.tsx │ │ │ │ └── video.tsx │ │ │ ├── folder.tsx │ │ │ ├── github.tsx │ │ │ ├── globe.tsx │ │ │ ├── google.tsx │ │ │ ├── grip-vertical.tsx │ │ │ ├── home.tsx │ │ │ ├── index.tsx │ │ │ ├── linkedin.tsx │ │ │ ├── menu.tsx │ │ │ ├── moon.tsx │ │ │ ├── more-horizontal.tsx │ │ │ ├── more-vertical.tsx │ │ │ ├── papermark-sparkle.tsx │ │ │ ├── passkey.tsx │ │ │ ├── pie-chart.tsx │ │ │ ├── portrait-landscape.tsx │ │ │ ├── producthunt.tsx │ │ │ ├── search.tsx │ │ │ ├── settings.tsx │ │ │ ├── slack-icon.tsx │ │ │ ├── sparkle.tsx │ │ │ ├── sun.tsx │ │ │ ├── teams.tsx │ │ │ ├── twitter.tsx │ │ │ ├── user-round.tsx │ │ │ ├── x-circle.tsx │ │ │ └── x.tsx │ │ └── logo-cloud.tsx │ ├── sidebar/ │ │ ├── app-sidebar.tsx │ │ ├── banners/ │ │ │ └── slack-banner.tsx │ │ ├── nav-main.tsx │ │ ├── nav-user.tsx │ │ └── team-switcher.tsx │ ├── sidebar-folders.tsx │ ├── tab-menu.tsx │ ├── tags/ │ │ └── add-tag-modal.tsx │ ├── teams/ │ │ ├── add-team-member-modal.tsx │ │ ├── add-team-modal.tsx │ │ ├── delete-team-modal.tsx │ │ └── select-team.tsx │ ├── theme-provider.tsx │ ├── theme-toggle.tsx │ ├── ui/ │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── bar-list.tsx │ │ ├── breadcrumb.tsx │ │ ├── button-group.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── copy-button.tsx │ │ ├── devices.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── feature-preview.tsx │ │ ├── file-upload.tsx │ │ ├── form-hook.tsx │ │ ├── form.tsx │ │ ├── gauge.tsx │ │ ├── hover-card.tsx │ │ ├── input-group.tsx │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── loading-dots.module.css │ │ ├── loading-dots.tsx │ │ ├── loading-spinner.module.css │ │ ├── loading-spinner.tsx │ │ ├── modal.tsx │ │ ├── multi-select-v2.tsx │ │ ├── nextra-filetree.tsx │ │ ├── pagination.tsx │ │ ├── phone-input.tsx │ │ ├── popover.tsx │ │ ├── portal.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── responsive-button.tsx │ │ ├── rich-text-editor.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── single-select.tsx │ │ ├── skeleton.tsx │ │ ├── smart-date-time-picker.tsx │ │ ├── sonner.tsx │ │ ├── status-badge.tsx │ │ ├── switch.tsx │ │ ├── tab-select.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── timestamp-tooltip.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip.tsx │ │ └── upgrade-button.tsx │ ├── upload-notification.tsx │ ├── upload-zone.tsx │ ├── user-agent-icon.tsx │ ├── view/ │ │ ├── ScreenProtection.tsx │ │ ├── access-form/ │ │ │ ├── access-form-theme.tsx │ │ │ ├── agreement-section.tsx │ │ │ ├── custom-fields-section.tsx │ │ │ ├── email-section.tsx │ │ │ ├── email-verification-form.tsx │ │ │ ├── index.tsx │ │ │ ├── name-section.tsx │ │ │ └── password-section.tsx │ │ ├── annotations/ │ │ │ ├── annotation-panel.tsx │ │ │ └── annotation-toggle.tsx │ │ ├── conversations/ │ │ │ └── sidebar.tsx │ │ ├── custom-metatag.tsx │ │ ├── dataroom/ │ │ │ ├── dataroom-document-view.tsx │ │ │ ├── dataroom-view.tsx │ │ │ ├── document-card.tsx │ │ │ ├── document-upload-modal.tsx │ │ │ ├── download-otp-verification.tsx │ │ │ ├── downloads-panel.tsx │ │ │ ├── folder-card.tsx │ │ │ ├── index-file-dialog.tsx │ │ │ ├── introduction-modal.tsx │ │ │ ├── nav-dataroom.tsx │ │ │ ├── pending-document-card.tsx │ │ │ └── viewer-download-progress-modal.tsx │ │ ├── document-view.tsx │ │ ├── link-preview.tsx │ │ ├── nav.tsx │ │ ├── powered-by.tsx │ │ ├── question.tsx │ │ ├── report-form.tsx │ │ ├── toolbar.tsx │ │ ├── view-data.tsx │ │ ├── viewer/ │ │ │ ├── advanced-excel-viewer.tsx │ │ │ ├── away-poster.tsx │ │ │ ├── dataroom-viewer.tsx │ │ │ ├── download-only-viewer.tsx │ │ │ ├── excel-viewer.tsx │ │ │ ├── image-viewer.tsx │ │ │ ├── notion-page.tsx │ │ │ ├── pages-horizontal-viewer.tsx │ │ │ ├── pages-vertical-viewer.tsx │ │ │ ├── pdf-default-viewer.tsx │ │ │ ├── video-player.tsx │ │ │ ├── video-viewer.tsx │ │ │ └── viewer-surface-theme.tsx │ │ ├── visitor-graph.tsx │ │ └── watermark-svg.tsx │ ├── viewer-upload-component.tsx │ ├── viewer-upload-zone.tsx │ ├── visitors/ │ │ ├── contacts-document-table.tsx │ │ ├── contacts-table.tsx │ │ ├── data-table-pagination.tsx │ │ ├── dataroom-view-stats.tsx │ │ ├── dataroom-viewers.tsx │ │ ├── dataroom-visitor-custom-fields.tsx │ │ ├── dataroom-visitor-useragent.tsx │ │ ├── dataroom-visitors-history.tsx │ │ ├── dataroom-visitors-table.tsx │ │ ├── document-view-stats.tsx │ │ ├── visitor-avatar.tsx │ │ ├── visitor-chart.tsx │ │ ├── visitor-clicks.tsx │ │ ├── visitor-custom-fields.tsx │ │ ├── visitor-group-modal.tsx │ │ ├── visitor-groups-section.tsx │ │ ├── visitor-useragent-base.tsx │ │ ├── visitor-useragent-placeholder.tsx │ │ ├── visitor-useragent.tsx │ │ ├── visitor-video-chart.tsx │ │ └── visitors-table.tsx │ ├── webhooks/ │ │ └── webhook-events.tsx │ ├── welcome/ │ │ ├── containers/ │ │ │ ├── link-option-container.tsx │ │ │ ├── onboarding-dataroom-link-options.tsx │ │ │ ├── onboarding-link-options.tsx │ │ │ └── upload-container.tsx │ │ ├── dataroom-ai-generate.tsx │ │ ├── dataroom-choice.tsx │ │ ├── dataroom-trial.tsx │ │ ├── dataroom-upload.tsx │ │ ├── dataroom.tsx │ │ ├── intro.tsx │ │ ├── next.tsx │ │ ├── notion-form.tsx │ │ ├── select.tsx │ │ ├── special-upload.tsx │ │ └── upload.tsx │ └── yearly-recap/ │ ├── globe.tsx │ ├── index.ts │ ├── yearly-recap-banner.tsx │ └── yearly-recap-modal.tsx ├── components.json ├── context/ │ ├── pending-uploads-context.tsx │ └── team-context.tsx ├── ee/ │ ├── LICENSE.md │ ├── README.md │ ├── emails/ │ │ └── pause-resume-reminder.tsx │ ├── features/ │ │ ├── access-notifications/ │ │ │ ├── components/ │ │ │ │ └── blocked-email-attempt.tsx │ │ │ ├── index.ts │ │ │ └── lib/ │ │ │ ├── report-denied-access-attempt.ts │ │ │ └── send-blocked-email-attempt.ts │ │ ├── ai/ │ │ │ ├── components/ │ │ │ │ ├── agents-settings-card.tsx │ │ │ │ ├── ai-indexing-status.tsx │ │ │ │ ├── chat-message.tsx │ │ │ │ ├── document-ai-dialog.tsx │ │ │ │ ├── document-context-selector.tsx │ │ │ │ ├── viewer-chat-panel.tsx │ │ │ │ ├── viewer-chat-provider.tsx │ │ │ │ ├── viewer-chat-toggle.tsx │ │ │ │ └── viewer-thread-selector.tsx │ │ │ ├── hooks/ │ │ │ │ └── use-ai-indexing-status.ts │ │ │ ├── lib/ │ │ │ │ ├── chat/ │ │ │ │ │ ├── create-chat.ts │ │ │ │ │ ├── generate-chat-title.ts │ │ │ │ │ ├── get-filtered-dataroom-document-ids.ts │ │ │ │ │ └── send-message.ts │ │ │ │ ├── file-processing/ │ │ │ │ │ ├── extract-document-metadata.ts │ │ │ │ │ └── process-document-for-vector-store.ts │ │ │ │ ├── models/ │ │ │ │ │ ├── google.ts │ │ │ │ │ └── openai.ts │ │ │ │ ├── permissions/ │ │ │ │ │ └── validate-chat-access.ts │ │ │ │ ├── stream/ │ │ │ │ │ └── parse-text-stream.ts │ │ │ │ ├── trigger/ │ │ │ │ │ ├── add-file-to-vector-store.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── process-document-for-ai.ts │ │ │ │ │ ├── process-excel-for-ai.ts │ │ │ │ │ ├── process-image-for-ai.ts │ │ │ │ │ ├── process-pdf-for-ai.ts │ │ │ │ │ └── types.ts │ │ │ │ └── vector-stores/ │ │ │ │ ├── create-dataroom-vector-store.ts │ │ │ │ ├── create-team-vector-store.ts │ │ │ │ ├── delete-vector-store.ts │ │ │ │ ├── get-vector-store-info.ts │ │ │ │ ├── remove-file-from-vector-store.ts │ │ │ │ └── upload-file-to-vector-store.ts │ │ │ └── schemas/ │ │ │ └── chat.ts │ │ ├── billing/ │ │ │ ├── cancellation/ │ │ │ │ ├── api/ │ │ │ │ │ ├── automatic-unpause-route.ts │ │ │ │ │ ├── cancel-route.ts │ │ │ │ │ ├── cancellation-feedback-route.ts │ │ │ │ │ ├── pause-route.ts │ │ │ │ │ ├── reactivate-route.ts │ │ │ │ │ ├── retention-route.ts │ │ │ │ │ ├── send-pause-resume-notification.ts │ │ │ │ │ └── unpause-route.ts │ │ │ │ ├── components/ │ │ │ │ │ ├── cancellation-modal.tsx │ │ │ │ │ ├── confirm-cancellation-modal.tsx │ │ │ │ │ ├── feedback-modal.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── pause-subscription-modal.tsx │ │ │ │ │ ├── reason-base-modal.tsx │ │ │ │ │ ├── retention-offer-modal.tsx │ │ │ │ │ └── schedule-call-modal.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── emails/ │ │ │ │ │ ├── components/ │ │ │ │ │ │ └── pause-resume-reminder.tsx │ │ │ │ │ └── lib/ │ │ │ │ │ └── send-email-pause-resume-reminder.ts │ │ │ │ └── lib/ │ │ │ │ ├── constants.ts │ │ │ │ ├── is-team-paused.ts │ │ │ │ └── trigger/ │ │ │ │ ├── pause-resume-notification.ts │ │ │ │ └── unpause-task.ts │ │ │ └── renewal-reminder/ │ │ │ ├── emails/ │ │ │ │ └── subscription-renewal-reminder.tsx │ │ │ └── lib/ │ │ │ └── send-subscription-renewal-reminder.ts │ │ ├── conversations/ │ │ │ ├── api/ │ │ │ │ ├── conversations-route.ts │ │ │ │ ├── send-conversation-new-message-notification.ts │ │ │ │ ├── send-conversation-team-member-notification.ts │ │ │ │ ├── team-conversations-route.ts │ │ │ │ ├── team-faqs-route.ts │ │ │ │ └── toggle-conversations-route.ts │ │ │ ├── components/ │ │ │ │ ├── dashboard/ │ │ │ │ │ ├── conversation-list-item.tsx │ │ │ │ │ ├── conversations-not-enabled-banner.tsx │ │ │ │ │ ├── edit-faq-modal.tsx │ │ │ │ │ ├── link-option-conversation-section.tsx │ │ │ │ │ └── publish-faq-modal.tsx │ │ │ │ ├── shared/ │ │ │ │ │ ├── conversation-document-context.tsx │ │ │ │ │ └── conversation-message.tsx │ │ │ │ └── viewer/ │ │ │ │ ├── conversation-view-sidebar.tsx │ │ │ │ └── faq-section.tsx │ │ │ ├── emails/ │ │ │ │ ├── components/ │ │ │ │ │ ├── conversation-notification.tsx │ │ │ │ │ └── conversation-team-notification.tsx │ │ │ │ └── lib/ │ │ │ │ ├── send-conversation-notification.ts │ │ │ │ └── send-conversation-team-notification.ts │ │ │ ├── lib/ │ │ │ │ ├── api/ │ │ │ │ │ ├── conversations/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── messages/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── notifications/ │ │ │ │ │ └── index.ts │ │ │ │ ├── schemas/ │ │ │ │ │ └── faq.ts │ │ │ │ └── trigger/ │ │ │ │ └── conversation-message-notification.ts │ │ │ └── pages/ │ │ │ ├── conversation-detail.tsx │ │ │ ├── conversation-overview.tsx │ │ │ └── faq-overview.tsx │ │ ├── conversions/ │ │ │ └── python/ │ │ │ └── docx-sanitizer.py │ │ ├── dataroom-invitations/ │ │ │ ├── api/ │ │ │ │ ├── group-invite.ts │ │ │ │ ├── link-invite.ts │ │ │ │ └── uninvited.ts │ │ │ ├── components/ │ │ │ │ └── invite-viewers-modal.tsx │ │ │ ├── emails/ │ │ │ │ ├── components/ │ │ │ │ │ └── dataroom-viewer-invitation.tsx │ │ │ │ └── lib/ │ │ │ │ └── send-dataroom-viewer-invite.ts │ │ │ └── lib/ │ │ │ ├── schema/ │ │ │ │ └── dataroom-invitations.ts │ │ │ └── swr/ │ │ │ └── use-dataroom-invitations.ts │ │ ├── permissions/ │ │ │ └── components/ │ │ │ ├── dataroom-link-sheet.tsx │ │ │ └── permissions-sheet.tsx │ │ ├── security/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── fraud-prevention.ts │ │ │ │ └── ratelimit.ts │ │ │ └── sso/ │ │ │ ├── components/ │ │ │ │ ├── directory-sync-config-modal.tsx │ │ │ │ ├── saml-config-modal.tsx │ │ │ │ ├── sso-enforcement-toggle.tsx │ │ │ │ └── sso-login.tsx │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ └── product.ts │ │ ├── storage/ │ │ │ ├── config.ts │ │ │ └── s3-store.ts │ │ ├── templates/ │ │ │ ├── api/ │ │ │ │ └── datarooms/ │ │ │ │ ├── apply-template.ts │ │ │ │ └── generate.ts │ │ │ ├── components/ │ │ │ │ └── dataroom-templates.tsx │ │ │ ├── constants/ │ │ │ │ └── dataroom-templates.ts │ │ │ ├── lib/ │ │ │ │ └── prompts.ts │ │ │ └── schemas/ │ │ │ └── dataroom-templates.ts │ │ └── workflows/ │ │ ├── components/ │ │ │ ├── index.ts │ │ │ ├── step-form-dialog.tsx │ │ │ ├── step-list.tsx │ │ │ ├── workflow-access-view.tsx │ │ │ ├── workflow-empty-state.tsx │ │ │ ├── workflow-header.tsx │ │ │ └── workflow-list.tsx │ │ ├── index.ts │ │ ├── lib/ │ │ │ ├── engine.ts │ │ │ ├── types.ts │ │ │ └── validation.ts │ │ └── pages/ │ │ ├── workflow-detail.tsx │ │ ├── workflow-new.tsx │ │ └── workflow-overview.tsx │ ├── limits/ │ │ ├── constants.ts │ │ ├── handler.ts │ │ ├── server.ts │ │ └── swr-handler.ts │ └── stripe/ │ ├── client.ts │ ├── constants.ts │ ├── functions/ │ │ ├── get-coupon-from-plan.ts │ │ ├── get-display-name-from-plan.ts │ │ ├── get-price-id-from-plan.ts │ │ ├── get-quantity-from-plan.ts │ │ └── get-subscription-item.ts │ ├── index.ts │ ├── utils.ts │ └── webhooks/ │ ├── checkout-session-completed.ts │ ├── customer-subscription-deleted.ts │ ├── customer-subscription-updated.ts │ └── invoice-upcoming.ts ├── lib/ │ ├── analytics/ │ │ └── index.ts │ ├── api/ │ │ ├── auth/ │ │ │ ├── passkey.ts │ │ │ └── token.ts │ │ ├── documents/ │ │ │ └── process-document.ts │ │ ├── domains/ │ │ │ ├── clear-team-redirects.ts │ │ │ ├── redis.ts │ │ │ └── validate-redirect-url.ts │ │ ├── domains.ts │ │ ├── links/ │ │ │ └── link-data.ts │ │ ├── notification-helper.ts │ │ ├── teams/ │ │ │ └── is-saml-enforced-for-email-domain.ts │ │ └── views/ │ │ └── send-webhook-event.ts │ ├── auth/ │ │ ├── dataroom-auth.ts │ │ ├── link-session.ts │ │ └── preview-auth.ts │ ├── constants/ │ │ └── folder-constants.ts │ ├── constants.ts │ ├── cron/ │ │ ├── index.ts │ │ └── verify-qstash.ts │ ├── dataroom/ │ │ ├── build-folder-hierarchy.ts │ │ └── index-generator.ts │ ├── documents/ │ │ ├── create-document.ts │ │ ├── get-file-helper.ts │ │ ├── move-dataroom-documents.ts │ │ ├── move-dataroom-folders.ts │ │ ├── move-documents.ts │ │ └── move-folder.ts │ ├── domains.ts │ ├── dub.ts │ ├── edge-config/ │ │ ├── blacklist.ts │ │ ├── custom-email.ts │ │ └── trusted-teams.ts │ ├── emails/ │ │ ├── process-dataroom-digest.ts │ │ ├── send-custom-domain-setup.ts │ │ ├── send-dataroom-digest-notification.ts │ │ ├── send-dataroom-info.ts │ │ ├── send-dataroom-notification.ts │ │ ├── send-dataroom-trial-24h.ts │ │ ├── send-dataroom-trial-end.ts │ │ ├── send-dataroom-trial.ts │ │ ├── send-dataroom-upload-notification.ts │ │ ├── send-deleted-domain.ts │ │ ├── send-download-ready-email.ts │ │ ├── send-email-otp-verification.ts │ │ ├── send-export-ready-email.ts │ │ ├── send-hundred-views-congrats.ts │ │ ├── send-invalid-domain.ts │ │ ├── send-mail-verification.ts │ │ ├── send-onboarding.ts │ │ ├── send-slack-integration.ts │ │ ├── send-teammate-invite.ts │ │ ├── send-thousand-views-congrats.ts │ │ ├── send-upgrade-month-checkin.ts │ │ ├── send-upgrade-personal-welcome.ts │ │ ├── send-upgrade-plan.ts │ │ ├── send-upgrade-six-months-checkin.ts │ │ ├── send-verification-request.ts │ │ ├── send-viewed-dataroom-paused.ts │ │ ├── send-viewed-dataroom.ts │ │ ├── send-viewed-document-paused.ts │ │ ├── send-viewed-document.ts │ │ └── send-welcome.ts │ ├── errorHandler.ts │ ├── featureFlags/ │ │ └── index.ts │ ├── files/ │ │ ├── aws-client.ts │ │ ├── bulk-download-presign.ts │ │ ├── bulk-download.ts │ │ ├── copy-file-server.ts │ │ ├── copy-file-to-bucket-server.ts │ │ ├── delete-file-server.ts │ │ ├── delete-team-files-server.ts │ │ ├── get-file.ts │ │ ├── put-file-server.ts │ │ ├── put-file.ts │ │ ├── stream-file-server.ts │ │ ├── tus-redis-locker.ts │ │ ├── tus-upload.ts │ │ └── viewer-tus-upload.ts │ ├── folders/ │ │ └── create-folder.ts │ ├── hanko.ts │ ├── hooks/ │ │ ├── use-breakpoint.ts │ │ ├── use-copy-to-clipboard.ts │ │ ├── use-dataroom-permissions.ts │ │ ├── use-disable-print.ts │ │ ├── use-feature-flags.ts │ │ ├── use-is-admin.ts │ │ ├── use-lazy-pages.ts │ │ └── use-mobile.tsx │ ├── id-helper.ts │ ├── incoming-webhooks/ │ │ └── index.ts │ ├── integrations/ │ │ ├── install.ts │ │ └── slack/ │ │ ├── client.ts │ │ ├── env.ts │ │ ├── events.ts │ │ ├── install.ts │ │ ├── templates.ts │ │ ├── types.ts │ │ ├── uninstall.ts │ │ └── utils.ts │ ├── jackson.ts │ ├── middleware/ │ │ ├── app.ts │ │ ├── domain.ts │ │ ├── incoming-webhooks.ts │ │ └── posthog.ts │ ├── notion/ │ │ ├── config.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── openai.ts │ ├── posthog.ts │ ├── prisma.ts │ ├── redis/ │ │ └── dataroom-notification-queue.ts │ ├── redis-download-job-store.ts │ ├── redis-job-store.ts │ ├── redis.ts │ ├── resend.ts │ ├── sheet/ │ │ └── index.ts │ ├── swr/ │ │ ├── use-agreements.ts │ │ ├── use-annotations.ts │ │ ├── use-billing.ts │ │ ├── use-brand.ts │ │ ├── use-dataroom-document-stats.ts │ │ ├── use-dataroom-groups.ts │ │ ├── use-dataroom-permission-groups.ts │ │ ├── use-dataroom-stats.ts │ │ ├── use-dataroom-view-document-stats.ts │ │ ├── use-dataroom.ts │ │ ├── use-datarooms-simple.ts │ │ ├── use-datarooms.ts │ │ ├── use-document-overview.ts │ │ ├── use-document-preview.ts │ │ ├── use-document-stats.ts │ │ ├── use-document.ts │ │ ├── use-documents.ts │ │ ├── use-domains.ts │ │ ├── use-folders.ts │ │ ├── use-invitations.ts │ │ ├── use-invoices.ts │ │ ├── use-limits.ts │ │ ├── use-link.ts │ │ ├── use-passkeys.ts │ │ ├── use-saml.ts │ │ ├── use-scim.ts │ │ ├── use-slack-channels.ts │ │ ├── use-slack-integration.ts │ │ ├── use-stats.ts │ │ ├── use-tags.ts │ │ ├── use-team-ai.ts │ │ ├── use-team-settings.ts │ │ ├── use-team.ts │ │ ├── use-teams.ts │ │ ├── use-viewer.ts │ │ ├── use-viewers.ts │ │ └── use-visitor-groups.ts │ ├── team/ │ │ └── helper.ts │ ├── tinybird/ │ │ ├── README.md │ │ ├── datasources/ │ │ │ ├── click_events.datasource │ │ │ ├── page_views.datasource │ │ │ ├── pm_click_events.datasource │ │ │ ├── video_views.datasource │ │ │ └── webhook_events.datasource │ │ ├── endpoints/ │ │ │ ├── get_click_events_by_view.pipe │ │ │ ├── get_dataroom_view_document_stats.pipe │ │ │ ├── get_document_duration_per_viewer.pipe │ │ │ ├── get_page_duration_per_view.pipe │ │ │ ├── get_total_average_page_duration.pipe │ │ │ ├── get_total_dataroom_duration.pipe │ │ │ ├── get_total_document_duration.pipe │ │ │ ├── get_total_link_duration.pipe │ │ │ ├── get_total_team_duration.pipe │ │ │ ├── get_total_viewer_duration.pipe │ │ │ ├── get_useragent_per_view.pipe │ │ │ ├── get_video_events_by_document.pipe │ │ │ ├── get_video_events_by_view.pipe │ │ │ ├── get_view_completion_stats.pipe │ │ │ └── get_webhook_events.pipe │ │ ├── index.ts │ │ ├── pipes.ts │ │ └── publish.ts │ ├── tracking/ │ │ ├── record-link-view.ts │ │ ├── safe-page-view-tracker.ts │ │ ├── tracking-config.ts │ │ └── video-tracking.ts │ ├── trigger/ │ │ ├── automatic-unpause.ts │ │ ├── bulk-download.ts │ │ ├── cleanup-expired-exports.ts │ │ ├── conversation-message-notification.ts │ │ ├── convert-files.ts │ │ ├── dataroom-change-notification.ts │ │ ├── dataroom-upload-notification.ts │ │ ├── export-visits.ts │ │ ├── optimize-video-files.ts │ │ ├── pause-reminder-notification.ts │ │ ├── pdf-to-image-route.ts │ │ └── send-scheduled-email.ts │ ├── types/ │ │ ├── document-preview.ts │ │ └── index-file.ts │ ├── types.ts │ ├── unsend.ts │ ├── utils/ │ │ ├── calculate-hierarchical-indexes.ts │ │ ├── create-adaptive-surface-palette.ts │ │ ├── csv.ts │ │ ├── decode-base64url.ts │ │ ├── determine-text-color.ts │ │ ├── email-domain.ts │ │ ├── generate-checksum.ts │ │ ├── generate-jwt.ts │ │ ├── generate-otp.ts │ │ ├── generate-trigger-auth-token.ts │ │ ├── generate-trigger-status.ts │ │ ├── geo.ts │ │ ├── get-content-type.ts │ │ ├── get-file-icon.tsx │ │ ├── get-file-size-limits.ts │ │ ├── get-page-number-count.ts │ │ ├── get-search-params.ts │ │ ├── global-block-list.ts │ │ ├── hierarchical-display.ts │ │ ├── ip.ts │ │ ├── link-url.ts │ │ ├── reliable-tracking.ts │ │ ├── resize-image.ts │ │ ├── sanitize-html.ts │ │ ├── sort-items-by-index-name.ts │ │ ├── trigger-utils.ts │ │ ├── unsubscribe.ts │ │ ├── use-at-bottom.ts │ │ ├── use-copy-to-clipboard.ts │ │ ├── use-enter-submit.ts │ │ ├── use-media-query.ts │ │ ├── use-progress-status.ts │ │ ├── user-agent.ts │ │ └── validate-email.ts │ ├── utils.ts │ ├── webhook/ │ │ ├── constants.ts │ │ ├── send-webhooks.ts │ │ ├── signature.ts │ │ ├── transform.ts │ │ ├── triggers/ │ │ │ ├── document-created.ts │ │ │ └── link-created.ts │ │ └── types.ts │ ├── webstorage.ts │ ├── year-in-review/ │ │ ├── calculate-percentile.ts │ │ ├── get-stats.ts │ │ ├── index.ts │ │ └── send-emails.ts │ └── zod/ │ ├── schemas/ │ │ ├── folders.ts │ │ ├── multipart.ts │ │ ├── notifications.ts │ │ ├── presets.ts │ │ └── webhooks.ts │ └── url-validation.ts ├── middleware.ts ├── next.config.mjs ├── package.json ├── pages/ │ ├── 404.tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── account/ │ │ ├── general.tsx │ │ └── security.tsx │ ├── api/ │ │ ├── account/ │ │ │ ├── index.ts │ │ │ └── passkeys.ts │ │ ├── analytics/ │ │ │ └── index.ts │ │ ├── auth/ │ │ │ └── [...nextauth].ts │ │ ├── conversations/ │ │ │ └── [[...conversations]].ts │ │ ├── feedback/ │ │ │ └── index.ts │ │ ├── file/ │ │ │ ├── browser-upload.ts │ │ │ ├── image-upload.ts │ │ │ ├── notion/ │ │ │ │ └── index.ts │ │ │ ├── s3/ │ │ │ │ ├── get-presigned-get-url-proxy.ts │ │ │ │ ├── get-presigned-get-url.ts │ │ │ │ ├── get-presigned-post-url.ts │ │ │ │ └── multipart.ts │ │ │ ├── tus/ │ │ │ │ └── [[...file]].ts │ │ │ └── tus-viewer/ │ │ │ └── [[...file]].ts │ │ ├── health.ts │ │ ├── internal/ │ │ │ └── billing/ │ │ │ └── automatic-unpause.ts │ │ ├── jobs/ │ │ │ ├── get-thumbnail.ts │ │ │ ├── process-download-batch.ts │ │ │ ├── send-conversation-new-message-notification.ts │ │ │ ├── send-conversation-team-member-notification.ts │ │ │ ├── send-dataroom-new-document-notification.ts │ │ │ ├── send-dataroom-upload-notification.ts │ │ │ ├── send-notification.ts │ │ │ └── send-pause-resume-notification.ts │ │ ├── links/ │ │ │ ├── [id]/ │ │ │ │ ├── annotations.ts │ │ │ │ ├── archive.ts │ │ │ │ ├── documents/ │ │ │ │ │ ├── [documentId]/ │ │ │ │ │ │ └── annotations.ts │ │ │ │ │ └── [documentId].ts │ │ │ │ ├── duplicate.ts │ │ │ │ ├── index.ts │ │ │ │ └── preview.ts │ │ │ ├── domains/ │ │ │ │ └── [...domainSlug].ts │ │ │ ├── download/ │ │ │ │ ├── [jobId].ts │ │ │ │ ├── bulk.ts │ │ │ │ ├── by-email.ts │ │ │ │ ├── dataroom-document.ts │ │ │ │ ├── dataroom-folder.ts │ │ │ │ ├── file/ │ │ │ │ │ └── [jobId]/ │ │ │ │ │ └── [partIndex].ts │ │ │ │ ├── index.ts │ │ │ │ ├── jobs.ts │ │ │ │ └── verify.ts │ │ │ ├── generate-index.ts │ │ │ └── index.ts │ │ ├── mupdf/ │ │ │ ├── annotate-document.ts │ │ │ ├── convert-page.ts │ │ │ └── get-pages.ts │ │ ├── notification-preferences/ │ │ │ └── dataroom.ts │ │ ├── passkeys/ │ │ │ └── register.ts │ │ ├── progress-token.ts │ │ ├── record_click.ts │ │ ├── record_reaction.ts │ │ ├── record_video_view.ts │ │ ├── record_view.ts │ │ ├── report.ts │ │ ├── revalidate.ts │ │ ├── stripe/ │ │ │ ├── webhook-old.ts │ │ │ └── webhook.ts │ │ ├── teams/ │ │ │ ├── [teamId]/ │ │ │ │ ├── agreements/ │ │ │ │ │ ├── [agreementId]/ │ │ │ │ │ │ ├── download.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── ai-settings.ts │ │ │ │ ├── billing/ │ │ │ │ │ ├── cancel.ts │ │ │ │ │ ├── cancellation-feedback.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── invoices.ts │ │ │ │ │ ├── manage.ts │ │ │ │ │ ├── pause.ts │ │ │ │ │ ├── plan.ts │ │ │ │ │ ├── reactivate.ts │ │ │ │ │ ├── retention-offer.ts │ │ │ │ │ ├── unpause.ts │ │ │ │ │ └── upgrade.ts │ │ │ │ ├── branding.ts │ │ │ │ ├── change-role.ts │ │ │ │ ├── datarooms/ │ │ │ │ │ ├── [id]/ │ │ │ │ │ │ ├── apply-permissions.ts │ │ │ │ │ │ ├── apply-template.ts │ │ │ │ │ │ ├── branding.ts │ │ │ │ │ │ ├── calculate-indexes.ts │ │ │ │ │ │ ├── conversations/ │ │ │ │ │ │ │ ├── [[...conversations]].ts │ │ │ │ │ │ │ └── toggle-conversations.ts │ │ │ │ │ │ ├── documents/ │ │ │ │ │ │ │ ├── [documentId]/ │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ └── stats.ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ └── move.ts │ │ │ │ │ │ ├── download/ │ │ │ │ │ │ │ ├── [jobId].ts │ │ │ │ │ │ │ ├── bulk.ts │ │ │ │ │ │ │ └── jobs.ts │ │ │ │ │ │ ├── duplicate.ts │ │ │ │ │ │ ├── export-visits.ts │ │ │ │ │ │ ├── faqs/ │ │ │ │ │ │ │ ├── [faqId].ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── folders/ │ │ │ │ │ │ │ ├── [...name].ts │ │ │ │ │ │ │ ├── documents/ │ │ │ │ │ │ │ │ └── [...name].ts │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── manage/ │ │ │ │ │ │ │ │ ├── [folderId]/ │ │ │ │ │ │ │ │ │ ├── dataroom-to-dataroom.ts │ │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ ├── move.ts │ │ │ │ │ │ │ └── parents/ │ │ │ │ │ │ │ └── [...name].ts │ │ │ │ │ │ ├── generate-index.ts │ │ │ │ │ │ ├── groups/ │ │ │ │ │ │ │ ├── [groupId]/ │ │ │ │ │ │ │ │ ├── export-visits.ts │ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ │ ├── invite.ts │ │ │ │ │ │ │ │ ├── links.ts │ │ │ │ │ │ │ │ ├── members/ │ │ │ │ │ │ │ │ │ ├── [memberId].ts │ │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ │ ├── permissions.ts │ │ │ │ │ │ │ │ ├── uninvited.ts │ │ │ │ │ │ │ │ └── views/ │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── links/ │ │ │ │ │ │ │ └── [linkId]/ │ │ │ │ │ │ │ └── invite.ts │ │ │ │ │ │ ├── links.ts │ │ │ │ │ │ ├── permission-groups/ │ │ │ │ │ │ │ ├── [permissionGroupId].ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── reorder.ts │ │ │ │ │ │ ├── stats.ts │ │ │ │ │ │ ├── users/ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── viewers/ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── views/ │ │ │ │ │ │ │ ├── [viewId]/ │ │ │ │ │ │ │ │ ├── custom-fields.ts │ │ │ │ │ │ │ │ ├── document-stats.ts │ │ │ │ │ │ │ │ ├── history.ts │ │ │ │ │ │ │ │ └── user-agent.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── views-count.ts │ │ │ │ │ ├── create-from-folder.ts │ │ │ │ │ ├── generate-ai-structure.ts │ │ │ │ │ ├── generate-ai.ts │ │ │ │ │ ├── generate.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── trial.ts │ │ │ │ ├── documents/ │ │ │ │ │ ├── [id]/ │ │ │ │ │ │ ├── add-to-dataroom.ts │ │ │ │ │ │ ├── advanced-mode.ts │ │ │ │ │ │ ├── annotations/ │ │ │ │ │ │ │ ├── [annotationId]/ │ │ │ │ │ │ │ │ └── images.ts │ │ │ │ │ │ │ ├── [annotationId].ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── change-orientation.ts │ │ │ │ │ │ ├── check-notion-accessibility.ts │ │ │ │ │ │ ├── duplicate.ts │ │ │ │ │ │ ├── export-visits.ts │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── links.ts │ │ │ │ │ │ ├── overview.ts │ │ │ │ │ │ ├── preview-data.ts │ │ │ │ │ │ ├── preview-pages.ts │ │ │ │ │ │ ├── stats.ts │ │ │ │ │ │ ├── toggle-dark-mode.ts │ │ │ │ │ │ ├── toggle-download-only.ts │ │ │ │ │ │ ├── update-link-url.ts │ │ │ │ │ │ ├── update-name.ts │ │ │ │ │ │ ├── update-notion-url.ts │ │ │ │ │ │ ├── versions/ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ ├── video-analytics.ts │ │ │ │ │ │ ├── views/ │ │ │ │ │ │ │ ├── [viewId]/ │ │ │ │ │ │ │ │ ├── click-events.ts │ │ │ │ │ │ │ │ ├── custom-fields.ts │ │ │ │ │ │ │ │ ├── stats.ts │ │ │ │ │ │ │ │ ├── user-agent.ts │ │ │ │ │ │ │ │ └── video-stats.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── views-count.ts │ │ │ │ │ ├── agreement.ts │ │ │ │ │ ├── document-processing-status.ts │ │ │ │ │ ├── hidden/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── hide.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── move.ts │ │ │ │ │ ├── search.ts │ │ │ │ │ └── update.ts │ │ │ │ ├── domains/ │ │ │ │ │ ├── [domain]/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── validate.ts │ │ │ │ │ │ └── verify.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── enable-advanced-mode.ts │ │ │ │ ├── export-jobs/ │ │ │ │ │ ├── [exportId]/ │ │ │ │ │ │ └── send-email.ts │ │ │ │ │ └── [exportId].ts │ │ │ │ ├── export-jobs.ts │ │ │ │ ├── folders/ │ │ │ │ │ ├── [...name].ts │ │ │ │ │ ├── documents/ │ │ │ │ │ │ └── [...name].ts │ │ │ │ │ ├── hide.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── manage/ │ │ │ │ │ │ ├── [folderId]/ │ │ │ │ │ │ │ ├── add-to-dataroom.ts │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── move.ts │ │ │ │ │ └── parents/ │ │ │ │ │ └── [...name].ts │ │ │ │ ├── global-block-list.ts │ │ │ │ ├── ignored-domains.ts │ │ │ │ ├── incoming-webhooks/ │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── integrations/ │ │ │ │ │ └── slack/ │ │ │ │ │ ├── channels.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── invitations/ │ │ │ │ │ ├── accept.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ └── resend.ts │ │ │ │ ├── invite.ts │ │ │ │ ├── limits.ts │ │ │ │ ├── links/ │ │ │ │ │ └── [id]/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── visits.ts │ │ │ │ ├── presets/ │ │ │ │ │ ├── [id].ts │ │ │ │ │ └── index.ts │ │ │ │ ├── remove-teammate.ts │ │ │ │ ├── settings.ts │ │ │ │ ├── survey.ts │ │ │ │ ├── tags/ │ │ │ │ │ ├── [id]/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── tokens/ │ │ │ │ │ └── index.ts │ │ │ │ ├── update-advanced-mode.ts │ │ │ │ ├── update-name.ts │ │ │ │ ├── update-replicate-folders.ts │ │ │ │ ├── viewers/ │ │ │ │ │ ├── [id]/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── views/ │ │ │ │ │ └── [id]/ │ │ │ │ │ └── archive.ts │ │ │ │ ├── visitor-groups/ │ │ │ │ │ ├── [groupId]/ │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── webhooks/ │ │ │ │ │ ├── [id]/ │ │ │ │ │ │ ├── events.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── workflow-links.ts │ │ │ │ └── yearly-recap.ts │ │ │ └── index.ts │ │ ├── unsubscribe/ │ │ │ ├── dataroom/ │ │ │ │ └── index.ts │ │ │ └── yir/ │ │ │ └── index.ts │ │ ├── user/ │ │ │ └── subscribe.ts │ │ └── webhooks/ │ │ └── services/ │ │ └── [...path]/ │ │ └── index.ts │ ├── branding.tsx │ ├── dashboard.tsx │ ├── datarooms/ │ │ ├── [id]/ │ │ │ ├── analytics/ │ │ │ │ └── index.tsx │ │ │ ├── branding/ │ │ │ │ └── index.tsx │ │ │ ├── conversations/ │ │ │ │ ├── [conversationId]/ │ │ │ │ │ └── index.tsx │ │ │ │ ├── faqs/ │ │ │ │ │ └── index.tsx │ │ │ │ └── index.tsx │ │ │ ├── documents/ │ │ │ │ ├── [...name].tsx │ │ │ │ └── index.tsx │ │ │ ├── groups/ │ │ │ │ ├── [groupId]/ │ │ │ │ │ ├── group-analytics.tsx │ │ │ │ │ ├── index.tsx │ │ │ │ │ ├── links.tsx │ │ │ │ │ ├── members.tsx │ │ │ │ │ └── permissions.tsx │ │ │ │ └── index.tsx │ │ │ ├── index.tsx │ │ │ ├── permissions/ │ │ │ │ └── index.tsx │ │ │ ├── settings/ │ │ │ │ ├── downloads.tsx │ │ │ │ ├── file-permissions.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── introduction.tsx │ │ │ │ └── notifications.tsx │ │ │ └── users/ │ │ │ └── index.tsx │ │ └── index.tsx │ ├── documents/ │ │ ├── [id]/ │ │ │ └── index.tsx │ │ ├── hidden.tsx │ │ ├── index.tsx │ │ ├── new.tsx │ │ └── tree/ │ │ └── [...name].tsx │ ├── entrance_ppreview_demo.tsx │ ├── nav_ppreview_demo.tsx │ ├── notification-preferences.tsx │ ├── room_ppreview_demo.tsx │ ├── settings/ │ │ ├── agreements.tsx │ │ ├── ai.tsx │ │ ├── billing/ │ │ │ └── invoices.tsx │ │ ├── billing.tsx │ │ ├── domains.tsx │ │ ├── general.tsx │ │ ├── incoming-webhooks.tsx │ │ ├── people.tsx │ │ ├── presets/ │ │ │ ├── [id].tsx │ │ │ ├── index.tsx │ │ │ └── new.tsx │ │ ├── security.tsx │ │ ├── slack.tsx │ │ ├── tags.tsx │ │ ├── tokens.tsx │ │ ├── upgrade-holiday-offer.tsx │ │ ├── upgrade.tsx │ │ └── webhooks/ │ │ ├── [id]/ │ │ │ └── index.tsx │ │ ├── index.tsx │ │ └── new.tsx │ ├── unsubscribe.tsx │ ├── view/ │ │ ├── [linkId]/ │ │ │ ├── d/ │ │ │ │ └── [documentId].tsx │ │ │ ├── downloads.tsx │ │ │ ├── embed.tsx │ │ │ └── index.tsx │ │ └── domains/ │ │ └── [domain]/ │ │ └── [slug]/ │ │ ├── d/ │ │ │ └── [documentId].tsx │ │ ├── downloads.tsx │ │ └── index.tsx │ ├── visitors/ │ │ ├── [id]/ │ │ │ └── index.tsx │ │ └── index.tsx │ ├── welcome.tsx │ └── workflows/ │ ├── [id].tsx │ ├── index.tsx │ └── new.tsx ├── pkgx.yaml ├── postcss.config.js ├── prettier.config.js ├── prisma/ │ ├── README.md │ ├── add-migration.sh │ ├── migrations/ │ │ ├── 20230912150657_initialize/ │ │ │ └── migration.sql │ │ ├── 202310122339_NewColumnInLinkTable/ │ │ │ └── migration.sql │ │ ├── 20231013165123_create_document_version/ │ │ │ └── migration.sql │ │ ├── 20231014200337_create_document_pages/ │ │ │ └── migration.sql │ │ ├── 202310311254_NewColumnEnableNotificationLinkTable/ │ │ │ └── migration.sql │ │ ├── 20231105152632_create_team/ │ │ │ └── migration.sql │ │ ├── 20231113051339_create_sent_email/ │ │ │ └── migration.sql │ │ ├── 20231114054509_add_domain_to_sent_emails/ │ │ │ └── migration.sql │ │ ├── 20231116093816_update_invitations/ │ │ │ └── migration.sql │ │ ├── 20231127062841_add_conversation/ │ │ │ └── migration.sql │ │ ├── 20231128064540_add_indices/ │ │ │ └── migration.sql │ │ ├── 20231204070250_remove_trial/ │ │ │ └── migration.sql │ │ ├── 20231207081407_add_reactions/ │ │ │ └── migration.sql │ │ ├── 20240110233134_add_disable_feedback/ │ │ │ └── migration.sql │ │ ├── 20240117020456_add_branding/ │ │ │ └── migration.sql │ │ ├── 20240202052149_add_email_authentication_to_link_and_view/ │ │ │ └── migration.sql │ │ ├── 20240205170242_embedded_links/ │ │ │ └── migration.sql │ │ ├── 20240212081614_add_downloaded_time_to_view/ │ │ │ └── migration.sql │ │ ├── 20240215035046_add_allow_deny_list_to_links/ │ │ │ └── migration.sql │ │ ├── 20240221042933_add_document_storage_type_enum/ │ │ │ └── migration.sql │ │ ├── 20240313100203_add_folders/ │ │ │ └── migration.sql │ │ ├── 20240327102407_add_dataroom/ │ │ │ └── migration.sql │ │ ├── 20240330062000_add_viewtype/ │ │ │ └── migration.sql │ │ ├── 20240401000000_add_dataroom_brand/ │ │ │ └── migration.sql │ │ ├── 20240408000000_add_viewer/ │ │ │ └── migration.sql │ │ ├── 20240415000000_add_feedback/ │ │ │ └── migration.sql │ │ ├── 20240424152839_add_screenprotection_to_link/ │ │ │ └── migration.sql │ │ ├── 20240511000000_add_team_limits/ │ │ │ └── migration.sql │ │ ├── 20240520000000_add_vertical_to_document_version/ │ │ │ └── migration.sql │ │ ├── 20240521000000_add_manager_role/ │ │ │ └── migration.sql │ │ ├── 20240611000000_add_agreements/ │ │ │ └── migration.sql │ │ ├── 20240712000000_add_page_metadata/ │ │ │ └── migration.sql │ │ ├── 20240720000000_change_owner_dependecy/ │ │ │ └── migration.sql │ │ ├── 20240730000000_update_link_defaults/ │ │ │ └── migration.sql │ │ ├── 20240731000000_add_link_show_banner/ │ │ │ └── migration.sql │ │ ├── 20240809000000_add_dataroom_order_index/ │ │ │ └── migration.sql │ │ ├── 20240821000000_add_require_name/ │ │ │ └── migration.sql │ │ ├── 20240830000000_add_watermarks/ │ │ │ └── migration.sql │ │ ├── 20240901000000_add_domain_default/ │ │ │ └── migration.sql │ │ ├── 20240902000000_add_link_presets/ │ │ │ └── migration.sql │ │ ├── 20240911000000_add_dataroom_groups_permissions/ │ │ │ └── migration.sql │ │ ├── 20240915000000_add_advanced_mode/ │ │ │ └── migration.sql │ │ ├── 20240916000000_add_content_type_to_document/ │ │ │ └── migration.sql │ │ ├── 20240921000000_add_viewer_migration/ │ │ │ └── migration.sql │ │ ├── 20241004024010_add_favicon_column/ │ │ │ └── migration.sql │ │ ├── 20241020000000_add_teamid_to_link_and_view/ │ │ │ └── migration.sql │ │ ├── 20241029000000_add_archived_view/ │ │ │ └── migration.sql │ │ ├── 20241107000000_add_tokens_and_webhooks/ │ │ │ └── migration.sql │ │ ├── 20241118000000_add_filesize_and_downloadonly/ │ │ │ └── migration.sql │ │ ├── 20241123000000_add_viewer_notification_preferences/ │ │ │ └── migration.sql │ │ ├── 20241126000000_add_screen_shield/ │ │ │ └── migration.sql │ │ ├── 20241208000000_add_webhooks/ │ │ │ └── migration.sql │ │ ├── 20241212000000_add_yir/ │ │ │ └── migration.sql │ │ ├── 20241231_add_dataroom_allow_bulk_download/ │ │ │ └── migration.sql │ │ ├── 20250106000000_add_download_type/ │ │ │ └── migration.sql │ │ ├── 20250110000000_add_length_to_document_version/ │ │ │ └── migration.sql │ │ ├── 20250113000000_add_custom_fields/ │ │ │ └── migration.sql │ │ ├── 20250204000000_add_anonymous_group/ │ │ │ └── migration.sql │ │ ├── 20250217000000_add_contactid_to_user/ │ │ │ └── migration.sql │ │ ├── 20250217000000_remove_link_column/ │ │ │ └── migration.sql │ │ ├── 20250310000000_rename_conversation_table/ │ │ │ └── migration.sql │ │ ├── 20250404000000_add_questions_answer_conversation/ │ │ │ └── migration.sql │ │ ├── 20250413000000_add_dataroom_upload/ │ │ │ └── migration.sql │ │ ├── 20250425000000_update_link_presets/ │ │ │ └── migration.sql │ │ ├── 20250428000000_add_tags/ │ │ │ └── migration.sql │ │ ├── 20250502000000_add_additional_present_fields/ │ │ │ └── migration.sql │ │ ├── 20250511000000_add_index_file_to_links/ │ │ │ └── migration.sql │ │ ├── 20250513000000_add_notification_to_dataroom/ │ │ │ └── migration.sql │ │ ├── 20250516000000_add_advanced_mode_to_team/ │ │ │ └── migration.sql │ │ ├── 20250526000000_add_status_to_users/ │ │ │ └── migration.sql │ │ ├── 20250607000000_add_notification_to_presets/ │ │ │ └── migration.sql │ │ ├── 20250612000000_add_permissions_to_link/ │ │ │ └── migration.sql │ │ ├── 20250624042156_viewers_performance_optimization/ │ │ │ └── migration.sql │ │ ├── 20250711000000_add_ignored_domains_to_team/ │ │ │ └── migration.sql │ │ ├── 20250715042921_add_global_block_list_to_team/ │ │ │ └── migration.sql │ │ ├── 20250716000000_add_dataroom_permission_defaults/ │ │ │ └── migration.sql │ │ ├── 20250725000000_add_welcome_message_to_dataroom/ │ │ │ └── migration.sql │ │ ├── 20250730000000_change_file_size_bigint/ │ │ │ └── migration.sql │ │ ├── 20250806000000_add_billing_details_to_team/ │ │ │ └── migration.sql │ │ ├── 20250830000000_dataroom_faq_items_model/ │ │ │ └── migration.sql │ │ ├── 20250903000000_add_agreement_contenttype/ │ │ │ └── migration.sql │ │ ├── 20250905000000_add_hierarchical_index_to_dataroom/ │ │ │ └── migration.sql │ │ ├── 20250913000000_add_integrations/ │ │ │ └── migration.sql │ │ ├── 20250915000000_add_annotations/ │ │ │ └── migration.sql │ │ ├── 20250915000000_add_performance_indexes/ │ │ │ └── migration.sql │ │ ├── 20250917000000_add_performance_index_dataroom/ │ │ │ └── migration.sql │ │ ├── 20251007000000_add_welcome_message_to_brand/ │ │ │ └── migration.sql │ │ ├── 20251009000000_update-replicate-folders/ │ │ │ └── migration.sql │ │ ├── 20251017000000_add_show_banner_to_presets/ │ │ │ └── migration.sql │ │ ├── 20251021000000_add_deleted_at_to_links/ │ │ │ └── migration.sql │ │ ├── 20251027000000_add_banner_to_brand/ │ │ │ └── migration.sql │ │ ├── 20251031000000_add_message_to_link/ │ │ │ └── migration.sql │ │ ├── 20251111000000_add_viewer_invitations/ │ │ │ └── migration.sql │ │ ├── 20251116000000_add_workflow/ │ │ │ └── migration.sql │ │ ├── 20251208000000_add_chat_model/ │ │ │ └── migration.sql │ │ ├── 20251209000000_update_view_model/ │ │ │ └── migration.sql │ │ ├── 20251230000000_add_timezone_to_team/ │ │ │ └── migration.sql │ │ ├── 20260113000000_add_hidden_in_all_documents/ │ │ │ └── migration.sql │ │ ├── 20260113000000_add_internal_name_to_dataroom/ │ │ │ └── migration.sql │ │ ├── 20260120000000_add_folder_icon_color/ │ │ │ └── migration.sql │ │ ├── 20260204000000_add_introduction_page/ │ │ │ └── migration.sql │ │ ├── 20260210000000_add_saml_scim_to_team/ │ │ │ └── migration.sql │ │ ├── 20260212000000_add_owner_to_link/ │ │ │ └── migration.sql │ │ ├── 20260212000000_add_visitor_groups/ │ │ │ └── migration.sql │ │ ├── 20260219171000_add_dataroom_bg_toggle_to_brand/ │ │ │ └── migration.sql │ │ ├── 20260220000000_add_redirect_url_to_domain/ │ │ │ └── migration.sql │ │ ├── 20260227000000_add_survey_data_to_team/ │ │ │ └── migration.sql │ │ └── migration_lock.toml │ └── schema/ │ ├── annotation.prisma │ ├── conversation.prisma │ ├── dataroom.prisma │ ├── document.prisma │ ├── integration.prisma │ ├── jackson.prisma │ ├── link.prisma │ ├── schema.prisma │ ├── team.prisma │ └── workflow.prisma ├── styles/ │ ├── custom-notion-styles.css │ ├── custom-viewer-styles.css │ └── globals.css ├── tailwind.config.js ├── trigger.config.ts ├── tsconfig.json └── vercel.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .agents/skills/frontend-design/LICENSE.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS ================================================ FILE: .agents/skills/frontend-design/SKILL.md ================================================ --- name: frontend-design description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, artifacts, posters, or applications (examples include websites, landing pages, dashboards, React components, HTML/CSS layouts, or when styling/beautifying any web UI). Generates creative, polished code and UI design that avoids generic AI aesthetics. license: Complete terms in LICENSE.txt --- This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices. The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints. ## Design Thinking Before coding, understand the context and commit to a BOLD aesthetic direction: - **Purpose**: What problem does this interface solve? Who uses it? - **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction. - **Constraints**: Technical requirements (framework, performance, accessibility). - **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember? **CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity. Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is: - Production-grade and functional - Visually striking and memorable - Cohesive with a clear aesthetic point-of-view - Meticulously refined in every detail ## Frontend Aesthetics Guidelines Focus on: - **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font. - **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes. - **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise. - **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density. - **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays. NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character. Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations. **IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well. Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision. ================================================ FILE: .agents/skills/gh-cli/SKILL.md ================================================ --- name: gh-cli description: GitHub CLI (gh) comprehensive reference for repositories, issues, pull requests, Actions, projects, releases, gists, codespaces, organizations, extensions, and all GitHub operations from the command line. --- # GitHub CLI (gh) Comprehensive reference for GitHub CLI (gh) - work seamlessly with GitHub from the command line. **Version:** 2.85.0 (current as of January 2026) ## Prerequisites ### Installation ```bash # macOS brew install gh # Linux curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null sudo apt update sudo apt install gh # Windows winget install --id GitHub.cli # Verify installation gh --version ``` ### Authentication ```bash # Interactive login (default: github.com) gh auth login # Login with specific hostname gh auth login --hostname enterprise.internal # Login with token gh auth login --with-token < mytoken.txt # Check authentication status gh auth status # Switch accounts gh auth switch --hostname github.com --user username # Logout gh auth logout --hostname github.com --user username ``` ### Setup Git Integration ```bash # Configure git to use gh as credential helper gh auth setup-git # View active token gh auth token # Refresh authentication scopes gh auth refresh --scopes write:org,read:public_key ``` ## CLI Structure ``` gh # Root command ├── auth # Authentication │ ├── login │ ├── logout │ ├── refresh │ ├── setup-git │ ├── status │ ├── switch │ └── token ├── browse # Open in browser ├── codespace # GitHub Codespaces │ ├── code │ ├── cp │ ├── create │ ├── delete │ ├── edit │ ├── jupyter │ ├── list │ ├── logs │ ├── ports │ ├── rebuild │ ├── ssh │ ├── stop │ └── view ├── gist # Gists │ ├── clone │ ├── create │ ├── delete │ ├── edit │ ├── list │ ├── rename │ └── view ├── issue # Issues │ ├── create │ ├── list │ ├── status │ ├── close │ ├── comment │ ├── delete │ ├── develop │ ├── edit │ ├── lock │ ├── pin │ ├── reopen │ ├── transfer │ ├── unlock │ └── view ├── org # Organizations │ └── list ├── pr # Pull Requests │ ├── create │ ├── list │ ├── status │ ├── checkout │ ├── checks │ ├── close │ ├── comment │ ├── diff │ ├── edit │ ├── lock │ ├── merge │ ├── ready │ ├── reopen │ ├── revert │ ├── review │ ├── unlock │ ├── update-branch │ └── view ├── project # Projects │ ├── close │ ├── copy │ ├── create │ ├── delete │ ├── edit │ ├── field-create │ ├── field-delete │ ├── field-list │ ├── item-add │ ├── item-archive │ ├── item-create │ ├── item-delete │ ├── item-edit │ ├── item-list │ ├── link │ ├── list │ ├── mark-template │ ├── unlink │ └── view ├── release # Releases │ ├── create │ ├── list │ ├── delete │ ├── delete-asset │ ├── download │ ├── edit │ ├── upload │ ├── verify │ ├── verify-asset │ └── view ├── repo # Repositories │ ├── create │ ├── list │ ├── archive │ ├── autolink │ ├── clone │ ├── delete │ ├── deploy-key │ ├── edit │ ├── fork │ ├── gitignore │ ├── license │ ├── rename │ ├── set-default │ ├── sync │ ├── unarchive │ └── view ├── cache # Actions caches │ ├── delete │ └── list ├── run # Workflow runs │ ├── cancel │ ├── delete │ ├── download │ ├── list │ ├── rerun │ ├── view │ └── watch ├── workflow # Workflows │ ├── disable │ ├── enable │ ├── list │ ├── run │ └── view ├── agent-task # Agent tasks ├── alias # Command aliases │ ├── delete │ ├── import │ ├── list │ └── set ├── api # API requests ├── attestation # Artifact attestations │ ├── download │ ├── trusted-root │ └── verify ├── completion # Shell completion ├── config # Configuration │ ├── clear-cache │ ├── get │ ├── list │ └── set ├── extension # Extensions │ ├── browse │ ├── create │ ├── exec │ ├── install │ ├── list │ ├── remove │ ├── search │ └── upgrade ├── gpg-key # GPG keys │ ├── add │ ├── delete │ └── list ├── label # Labels │ ├── clone │ ├── create │ ├── delete │ ├── edit │ └── list ├── preview # Preview features ├── ruleset # Rulesets │ ├── check │ ├── list │ └── view ├── search # Search │ ├── code │ ├── commits │ ├── issues │ ├── prs │ └── repos ├── secret # Secrets │ ├── delete │ ├── list │ └── set ├── ssh-key # SSH keys │ ├── add │ ├── delete │ └── list ├── status # Status overview └── variable # Variables ├── delete ├── get ├── list └── set ``` ## Configuration ### Global Configuration ```bash # List all configuration gh config list # Get specific configuration value gh config list git_protocol gh config get editor # Set configuration value gh config set editor vim gh config set git_protocol ssh gh config set prompt disabled gh config set pager "less -R" # Clear configuration cache gh config clear-cache ``` ### Environment Variables ```bash # GitHub token (for automation) export GH_TOKEN=ghp_xxxxxxxxxxxx # GitHub hostname export GH_HOST=github.com # Disable prompts export GH_PROMPT_DISABLED=true # Custom editor export GH_EDITOR=vim # Custom pager export GH_PAGER=less # HTTP timeout export GH_TIMEOUT=30 # Custom repository (override default) export GH_REPO=owner/repo # Custom git protocol export GH_ENTERPRISE_HOSTNAME=hostname ``` ## Authentication (gh auth) ### Login ```bash # Interactive login gh auth login # Web-based authentication gh auth login --web # With clipboard for OAuth code gh auth login --web --clipboard # With specific git protocol gh auth login --git-protocol ssh # With custom hostname (GitHub Enterprise) gh auth login --hostname enterprise.internal # Login with token from stdin gh auth login --with-token < token.txt # Insecure storage (plain text) gh auth login --insecure-storage ``` ### Status ```bash # Show all authentication status gh auth status # Show active account only gh auth status --active # Show specific hostname gh auth status --hostname github.com # Show token in output gh auth status --show-token # JSON output gh auth status --json hosts # Filter with jq gh auth status --json hosts --jq '.hosts | add' ``` ### Switch Accounts ```bash # Interactive switch gh auth switch # Switch to specific user/host gh auth switch --hostname github.com --user monalisa ``` ### Token ```bash # Print authentication token gh auth token # Token for specific host/user gh auth token --hostname github.com --user monalisa ``` ### Refresh ```bash # Refresh credentials gh auth refresh # Add scopes gh auth refresh --scopes write:org,read:public_key # Remove scopes gh auth refresh --remove-scopes delete_repo # Reset to default scopes gh auth refresh --reset-scopes # With clipboard gh auth refresh --clipboard ``` ### Setup Git ```bash # Setup git credential helper gh auth setup-git # Setup for specific host gh auth setup-git --hostname enterprise.internal # Force setup even if host not known gh auth setup-git --hostname enterprise.internal --force ``` ## Browse (gh browse) ```bash # Open repository in browser gh browse # Open specific path gh browse script/ gh browse main.go:312 # Open issue or PR gh browse 123 # Open commit gh browse 77507cd94ccafcf568f8560cfecde965fcfa63 # Open with specific branch gh browse main.go --branch bug-fix # Open different repository gh browse --repo owner/repo # Open specific pages gh browse --actions # Actions tab gh browse --projects # Projects tab gh browse --releases # Releases tab gh browse --settings # Settings page gh browse --wiki # Wiki page # Print URL instead of opening gh browse --no-browser ``` ## Repositories (gh repo) ### Create Repository ```bash # Create new repository gh repo create my-repo # Create with description gh repo create my-repo --description "My awesome project" # Create public repository gh repo create my-repo --public # Create private repository gh repo create my-repo --private # Create with homepage gh repo create my-repo --homepage https://example.com # Create with license gh repo create my-repo --license mit # Create with gitignore gh repo create my-repo --gitignore python # Initialize as template repository gh repo create my-repo --template # Create repository in organization gh repo create org/my-repo # Create without cloning locally gh repo create my-repo --source=. # Disable issues gh repo create my-repo --disable-issues # Disable wiki gh repo create my-repo --disable-wiki ``` ### Clone Repository ```bash # Clone repository gh repo clone owner/repo # Clone to specific directory gh repo clone owner/repo my-directory # Clone with different branch gh repo clone owner/repo --branch develop ``` ### List Repositories ```bash # List all repositories gh repo list # List repositories for owner gh repo list owner # Limit results gh repo list --limit 50 # Public repositories only gh repo list --public # Source repositories only (not forks) gh repo list --source # JSON output gh repo list --json name,visibility,owner # Table output gh repo list --limit 100 | tail -n +2 # Filter with jq gh repo list --json name --jq '.[].name' ``` ### View Repository ```bash # View repository details gh repo view # View specific repository gh repo view owner/repo # JSON output gh repo view --json name,description,defaultBranchRef # View in browser gh repo view --web ``` ### Edit Repository ```bash # Edit description gh repo edit --description "New description" # Set homepage gh repo edit --homepage https://example.com # Change visibility gh repo edit --visibility private gh repo edit --visibility public # Enable/disable features gh repo edit --enable-issues gh repo edit --disable-issues gh repo edit --enable-wiki gh repo edit --disable-wiki gh repo edit --enable-projects gh repo edit --disable-projects # Set default branch gh repo edit --default-branch main # Rename repository gh repo rename new-name # Archive repository gh repo archive gh repo unarchive ``` ### Delete Repository ```bash # Delete repository gh repo delete owner/repo # Confirm without prompt gh repo delete owner/repo --yes ``` ### Fork Repository ```bash # Fork repository gh repo fork owner/repo # Fork to organization gh repo fork owner/repo --org org-name # Clone after forking gh repo fork owner/repo --clone # Remote name for fork gh repo fork owner/repo --remote-name upstream ``` ### Sync Fork ```bash # Sync fork with upstream gh repo sync # Sync specific branch gh repo sync --branch feature # Force sync gh repo sync --force ``` ### Set Default Repository ```bash # Set default repository for current directory gh repo set-default # Set default explicitly gh repo set-default owner/repo # Unset default gh repo set-default --unset ``` ### Repository Autolinks ```bash # List autolinks gh repo autolink list # Add autolink gh repo autolink add \ --key-prefix JIRA- \ --url-template https://jira.example.com/browse/ # Delete autolink gh repo autolink delete 12345 ``` ### Repository Deploy Keys ```bash # List deploy keys gh repo deploy-key list # Add deploy key gh repo deploy-key add ~/.ssh/id_rsa.pub \ --title "Production server" \ --read-only # Delete deploy key gh repo deploy-key delete 12345 ``` ### Gitignore and License ```bash # View gitignore template gh repo gitignore # View license template gh repo license mit # License with full name gh repo license mit --fullname "John Doe" ``` ## Issues (gh issue) ### Create Issue ```bash # Create issue interactively gh issue create # Create with title gh issue create --title "Bug: Login not working" # Create with title and body gh issue create \ --title "Bug: Login not working" \ --body "Steps to reproduce..." # Create with body from file gh issue create --body-file issue.md # Create with labels gh issue create --title "Fix bug" --labels bug,high-priority # Create with assignees gh issue create --title "Fix bug" --assignee user1,user2 # Create in specific repository gh issue create --repo owner/repo --title "Issue title" # Create issue from web gh issue create --web ``` ### List Issues ```bash # List all open issues gh issue list # List all issues (including closed) gh issue list --state all # List closed issues gh issue list --state closed # Limit results gh issue list --limit 50 # Filter by assignee gh issue list --assignee username gh issue list --assignee @me # Filter by labels gh issue list --labels bug,enhancement # Filter by milestone gh issue list --milestone "v1.0" # Search/filter gh issue list --search "is:open is:issue label:bug" # JSON output gh issue list --json number,title,state,author # Table view gh issue list --json number,title,labels --jq '.[] | [.number, .title, .labels[].name] | @tsv' # Show comments count gh issue list --json number,title,comments --jq '.[] | [.number, .title, .comments]' # Sort by gh issue list --sort created --order desc ``` ### View Issue ```bash # View issue gh issue view 123 # View with comments gh issue view 123 --comments # View in browser gh issue view 123 --web # JSON output gh issue view 123 --json title,body,state,labels,comments # View specific fields gh issue view 123 --json title --jq '.title' ``` ### Edit Issue ```bash # Edit interactively gh issue edit 123 # Edit title gh issue edit 123 --title "New title" # Edit body gh issue edit 123 --body "New description" # Add labels gh issue edit 123 --add-label bug,high-priority # Remove labels gh issue edit 123 --remove-label stale # Add assignees gh issue edit 123 --add-assignee user1,user2 # Remove assignees gh issue edit 123 --remove-assignee user1 # Set milestone gh issue edit 123 --milestone "v1.0" ``` ### Close/Reopen Issue ```bash # Close issue gh issue close 123 # Close with comment gh issue close 123 --comment "Fixed in PR #456" # Reopen issue gh issue reopen 123 ``` ### Comment on Issue ```bash # Add comment gh issue comment 123 --body "This looks good!" # Edit comment gh issue comment 123 --edit 456789 --body "Updated comment" # Delete comment gh issue comment 123 --delete 456789 ``` ### Issue Status ```bash # Show issue status summary gh issue status # Status for specific repository gh issue status --repo owner/repo ``` ### Pin/Unpin Issues ```bash # Pin issue (pinned to repo dashboard) gh issue pin 123 # Unpin issue gh issue unpin 123 ``` ### Lock/Unlock Issue ```bash # Lock conversation gh issue lock 123 # Lock with reason gh issue lock 123 --reason off-topic # Unlock gh issue unlock 123 ``` ### Transfer Issue ```bash # Transfer to another repository gh issue transfer 123 --repo owner/new-repo ``` ### Delete Issue ```bash # Delete issue gh issue delete 123 # Confirm without prompt gh issue delete 123 --yes ``` ### Develop Issue (Draft PR) ```bash # Create draft PR from issue gh issue develop 123 # Create in specific branch gh issue develop 123 --branch fix/issue-123 # Create with base branch gh issue develop 123 --base main ``` ## Pull Requests (gh pr) ### Create Pull Request ```bash # Create PR interactively gh pr create # Create with title gh pr create --title "Feature: Add new functionality" # Create with title and body gh pr create \ --title "Feature: Add new functionality" \ --body "This PR adds..." # Fill body from template gh pr create --body-file .github/PULL_REQUEST_TEMPLATE.md # Set base branch gh pr create --base main # Set head branch (default: current branch) gh pr create --head feature-branch # Create draft PR gh pr create --draft # Add assignees gh pr create --assignee user1,user2 # Add reviewers gh pr create --reviewer user1,user2 # Add labels gh pr create --labels enhancement,feature # Link to issue gh pr create --issue 123 # Create in specific repository gh pr create --repo owner/repo # Open in browser after creation gh pr create --web ``` ### List Pull Requests ```bash # List open PRs gh pr list # List all PRs gh pr list --state all # List merged PRs gh pr list --state merged # List closed (not merged) PRs gh pr list --state closed # Filter by head branch gh pr list --head feature-branch # Filter by base branch gh pr list --base main # Filter by author gh pr list --author username gh pr list --author @me # Filter by assignee gh pr list --assignee username # Filter by labels gh pr list --labels bug,enhancement # Limit results gh pr list --limit 50 # Search gh pr list --search "is:open is:pr label:review-required" # JSON output gh pr list --json number,title,state,author,headRefName # Show check status gh pr list --json number,title,statusCheckRollup --jq '.[] | [.number, .title, .statusCheckRollup[]?.status]' # Sort by gh pr list --sort created --order desc ``` ### View Pull Request ```bash # View PR gh pr view 123 # View with comments gh pr view 123 --comments # View in browser gh pr view 123 --web # JSON output gh pr view 123 --json title,body,state,author,commits,files # View diff gh pr view 123 --json files --jq '.files[].path' # View with jq query gh pr view 123 --json title,state --jq '"\(.title): \(.state)"' ``` ### Checkout Pull Request ```bash # Checkout PR branch gh pr checkout 123 # Checkout with specific branch name gh pr checkout 123 --branch name-123 # Force checkout gh pr checkout 123 --force ``` ### Diff Pull Request ```bash # View PR diff gh pr diff 123 # View diff with color gh pr diff 123 --color always # Output to file gh pr diff 123 > pr-123.patch # View diff of specific files gh pr diff 123 --name-only ``` ### Merge Pull Request ```bash # Merge PR gh pr merge 123 # Merge with specific method gh pr merge 123 --merge gh pr merge 123 --squash gh pr merge 123 --rebase # Delete branch after merge gh pr merge 123 --delete-branch # Merge with comment gh pr merge 123 --subject "Merge PR #123" --body "Merging feature" # Merge draft PR gh pr merge 123 --admin # Force merge (skip checks) gh pr merge 123 --admin ``` ### Close Pull Request ```bash # Close PR (as draft, not merge) gh pr close 123 # Close with comment gh pr close 123 --comment "Closing due to..." ``` ### Reopen Pull Request ```bash # Reopen closed PR gh pr reopen 123 ``` ### Edit Pull Request ```bash # Edit interactively gh pr edit 123 # Edit title gh pr edit 123 --title "New title" # Edit body gh pr edit 123 --body "New description" # Add labels gh pr edit 123 --add-label bug,enhancement # Remove labels gh pr edit 123 --remove-label stale # Add assignees gh pr edit 123 --add-assignee user1,user2 # Remove assignees gh pr edit 123 --remove-assignee user1 # Add reviewers gh pr edit 123 --add-reviewer user1,user2 # Remove reviewers gh pr edit 123 --remove-reviewer user1 # Mark as ready for review gh pr edit 123 --ready ``` ### Ready for Review ```bash # Mark draft PR as ready gh pr ready 123 ``` ### Pull Request Checks ```bash # View PR checks gh pr checks 123 # Watch checks in real-time gh pr checks 123 --watch # Watch interval (seconds) gh pr checks 123 --watch --interval 5 ``` ### Comment on Pull Request ```bash # Add comment gh pr comment 123 --body "Looks good!" # Comment on specific line gh pr comment 123 --body "Fix this" \ --repo owner/repo \ --head-owner owner --head-branch feature # Edit comment gh pr comment 123 --edit 456789 --body "Updated" # Delete comment gh pr comment 123 --delete 456789 ``` ### Review Pull Request ```bash # Review PR (opens editor) gh pr review 123 # Approve PR gh pr review 123 --approve --body "LGTM!" # Request changes gh pr review 123 --request-changes \ --body "Please fix these issues" # Comment on PR gh pr review 123 --comment --body "Some thoughts..." # Dismiss review gh pr review 123 --dismiss ``` ### Update Branch ```bash # Update PR branch with latest base branch gh pr update-branch 123 # Force update gh pr update-branch 123 --force # Use merge strategy gh pr update-branch 123 --merge ``` ### Lock/Unlock Pull Request ```bash # Lock PR conversation gh pr lock 123 # Lock with reason gh pr lock 123 --reason off-topic # Unlock gh pr unlock 123 ``` ### Revert Pull Request ```bash # Revert merged PR gh pr revert 123 # Revert with specific branch name gh pr revert 123 --branch revert-pr-123 ``` ### Pull Request Status ```bash # Show PR status summary gh pr status # Status for specific repository gh pr status --repo owner/repo ``` ## GitHub Actions ### Workflow Runs (gh run) ```bash # List workflow runs gh run list # List for specific workflow gh run list --workflow "ci.yml" # List for specific branch gh run list --branch main # Limit results gh run list --limit 20 # JSON output gh run list --json databaseId,status,conclusion,headBranch # View run details gh run view 123456789 # View run with verbose logs gh run view 123456789 --log # View specific job gh run view 123456789 --job 987654321 # View in browser gh run view 123456789 --web # Watch run in real-time gh run watch 123456789 # Watch with interval gh run watch 123456789 --interval 5 # Rerun failed run gh run rerun 123456789 # Rerun specific job gh run rerun 123456789 --job 987654321 # Cancel run gh run cancel 123456789 # Delete run gh run delete 123456789 # Download run artifacts gh run download 123456789 # Download specific artifact gh run download 123456789 --name build # Download to directory gh run download 123456789 --dir ./artifacts ``` ### Workflows (gh workflow) ```bash # List workflows gh workflow list # View workflow details gh workflow view ci.yml # View workflow YAML gh workflow view ci.yml --yaml # View in browser gh workflow view ci.yml --web # Enable workflow gh workflow enable ci.yml # Disable workflow gh workflow disable ci.yml # Run workflow manually gh workflow run ci.yml # Run with inputs gh workflow run ci.yml \ --raw-field \ version="1.0.0" \ environment="production" # Run from specific branch gh workflow run ci.yml --ref develop ``` ### Action Caches (gh cache) ```bash # List caches gh cache list # List for specific branch gh cache list --branch main # List with limit gh cache list --limit 50 # Delete cache gh cache delete 123456789 # Delete all caches gh cache delete --all ``` ### Action Secrets (gh secret) ```bash # List secrets gh secret list # Set secret (prompts for value) gh secret set MY_SECRET # Set secret from environment echo "$MY_SECRET" | gh secret set MY_SECRET # Set secret for specific environment gh secret set MY_SECRET --env production # Set secret for organization gh secret set MY_SECRET --org orgname # Delete secret gh secret delete MY_SECRET # Delete from environment gh secret delete MY_SECRET --env production ``` ### Action Variables (gh variable) ```bash # List variables gh variable list # Set variable gh variable set MY_VAR "some-value" # Set variable for environment gh variable set MY_VAR "value" --env production # Set variable for organization gh variable set MY_VAR "value" --org orgname # Get variable value gh variable get MY_VAR # Delete variable gh variable delete MY_VAR # Delete from environment gh variable delete MY_VAR --env production ``` ## Projects (gh project) ```bash # List projects gh project list # List for owner gh project list --owner owner # Open projects gh project list --open # View project gh project view 123 # View project items gh project view 123 --format json # Create project gh project create --title "My Project" # Create in organization gh project create --title "Project" --org orgname # Create with readme gh project create --title "Project" --readme "Description here" # Edit project gh project edit 123 --title "New Title" # Delete project gh project delete 123 # Close project gh project close 123 # Copy project gh project copy 123 --owner target-owner --title "Copy" # Mark template gh project mark-template 123 # List fields gh project field-list 123 # Create field gh project field-create 123 --title "Status" --datatype single_select # Delete field gh project field-delete 123 --id 456 # List items gh project item-list 123 # Create item gh project item-create 123 --title "New item" # Add item to project gh project item-add 123 --owner-owner --repo repo --issue 456 # Edit item gh project item-edit 123 --id 456 --title "Updated title" # Delete item gh project item-delete 123 --id 456 # Archive item gh project item-archive 123 --id 456 # Link items gh project link 123 --id 456 --link-id 789 # Unlink items gh project unlink 123 --id 456 --link-id 789 # View project in browser gh project view 123 --web ``` ## Releases (gh release) ```bash # List releases gh release list # View latest release gh release view # View specific release gh release view v1.0.0 # View in browser gh release view v1.0.0 --web # Create release gh release create v1.0.0 \ --notes "Release notes here" # Create release with notes from file gh release create v1.0.0 --notes-file notes.md # Create release with target gh release create v1.0.0 --target main # Create release as draft gh release create v1.0.0 --draft # Create pre-release gh release create v1.0.0 --prerelease # Create release with title gh release create v1.0.0 --title "Version 1.0.0" # Upload asset to release gh release upload v1.0.0 ./file.tar.gz # Upload multiple assets gh release upload v1.0.0 ./file1.tar.gz ./file2.tar.gz # Upload with label (casing sensitive) gh release upload v1.0.0 ./file.tar.gz --casing # Delete release gh release delete v1.0.0 # Delete with cleanup tag gh release delete v1.0.0 --yes # Delete specific asset gh release delete-asset v1.0.0 file.tar.gz # Download release assets gh release download v1.0.0 # Download specific asset gh release download v1.0.0 --pattern "*.tar.gz" # Download to directory gh release download v1.0.0 --dir ./downloads # Download archive (zip/tar) gh release download v1.0.0 --archive zip # Edit release gh release edit v1.0.0 --notes "Updated notes" # Verify release signature gh release verify v1.0.0 # Verify specific asset gh release verify-asset v1.0.0 file.tar.gz ``` ## Gists (gh gist) ```bash # List gists gh gist list # List all gists (including private) gh gist list --public # Limit results gh gist list --limit 20 # View gist gh gist view abc123 # View gist files gh gist view abc123 --files # Create gist gh gist create script.py # Create gist with description gh gist create script.py --desc "My script" # Create public gist gh gist create script.py --public # Create multi-file gist gh gist create file1.py file2.py # Create from stdin echo "print('hello')" | gh gist create # Edit gist gh gist edit abc123 # Delete gist gh gist delete abc123 # Rename gist file gh gist rename abc123 --filename old.py new.py # Clone gist gh gist clone abc123 # Clone to directory gh gist clone abc123 my-directory ``` ## Codespaces (gh codespace) ```bash # List codespaces gh codespace list # Create codespace gh codespace create # Create with specific repository gh codespace create --repo owner/repo # Create with branch gh codespace create --branch develop # Create with specific machine gh codespace create --machine premiumLinux # View codespace details gh codespace view # SSH into codespace gh codespace ssh # SSH with specific command gh codespace ssh --command "cd /workspaces && ls" # Open codespace in browser gh codespace code # Open in VS Code gh codespace code --codec # Open with specific path gh codespace code --path /workspaces/repo # Stop codespace gh codespace stop # Delete codespace gh codespace delete # View logs gh codespace logs --tail 100 # View ports gh codespace ports # Forward port gh codespace cp 8080:8080 # Rebuild codespace gh codespace rebuild # Edit codespace gh codespace edit --machine standardLinux # Jupyter support gh codespace jupyter # Copy files to/from codespace gh codespace cp file.txt :/workspaces/file.txt gh codespace cp :/workspaces/file.txt ./file.txt ``` ## Organizations (gh org) ```bash # List organizations gh org list # List for user gh org list --user username # JSON output gh org list --json login,name,description # View organization gh org view orgname # View organization members gh org view orgname --json members --jq '.members[] | .login' ``` ## Search (gh search) ```bash # Search code gh search code "TODO" # Search in specific repository gh search code "TODO" --repo owner/repo # Search commits gh search commits "fix bug" # Search issues gh search issues "label:bug state:open" # Search PRs gh search prs "is:open is:pr review:required" # Search repositories gh search repos "stars:>1000 language:python" # Limit results gh search repos "topic:api" --limit 50 # JSON output gh search repos "stars:>100" --json name,description,stargazers # Order results gh search repos "language:rust" --order desc --sort stars # Search with extensions gh search code "import" --extension py # Web search (open in browser) gh search prs "is:open" --web ``` ## Labels (gh label) ```bash # List labels gh label list # Create label gh label create bug --color "d73a4a" --description "Something isn't working" # Create with hex color gh label create enhancement --color "#a2eeef" # Edit label gh label edit bug --name "bug-report" --color "ff0000" # Delete label gh label delete bug # Clone labels from repository gh label clone owner/repo # Clone to specific repository gh label clone owner/repo --repo target/repo ``` ## SSH Keys (gh ssh-key) ```bash # List SSH keys gh ssh-key list # Add SSH key gh ssh-key add ~/.ssh/id_rsa.pub --title "My laptop" # Add key with type gh ssh-key add ~/.ssh/id_ed25519.pub --type "authentication" # Delete SSH key gh ssh-key delete 12345 # Delete by title gh ssh-key delete --title "My laptop" ``` ## GPG Keys (gh gpg-key) ```bash # List GPG keys gh gpg-key list # Add GPG key gh gpg-key add ~/.ssh/id_rsa.pub # Delete GPG key gh gpg-key delete 12345 # Delete by key ID gh gpg-key delete ABCD1234 ``` ## Status (gh status) ```bash # Show status overview gh status # Status for specific repositories gh status --repo owner/repo # JSON output gh status --json ``` ## Configuration (gh config) ```bash # List all config gh config list # Get specific value gh config get editor # Set value gh config set editor vim # Set git protocol gh config set git_protocol ssh # Clear cache gh config clear-cache # Set prompt behavior gh config set prompt disabled gh config set prompt enabled ``` ## Extensions (gh extension) ```bash # List installed extensions gh extension list # Search extensions gh extension search github # Install extension gh extension install owner/extension-repo # Install from branch gh extension install owner/extension-repo --branch develop # Upgrade extension gh extension upgrade extension-name # Remove extension gh extension remove extension-name # Create new extension gh extension create my-extension # Browse extensions gh extension browse # Execute extension command gh extension exec my-extension --arg value ``` ## Aliases (gh alias) ```bash # List aliases gh alias list # Set alias gh alias set prview 'pr view --web' # Set shell alias gh alias set co 'pr checkout' --shell # Delete alias gh alias delete prview # Import aliases gh alias import ./aliases.sh ``` ## API Requests (gh api) ```bash # Make API request gh api /user # Request with method gh api --method POST /repos/owner/repo/issues \ --field title="Issue title" \ --field body="Issue body" # Request with headers gh api /user \ --header "Accept: application/vnd.github.v3+json" # Request with pagination gh api /user/repos --paginate # Raw output (no formatting) gh api /user --raw # Include headers in output gh api /user --include # Silent mode (no progress output) gh api /user --silent # Input from file gh api --input request.json # jq query on response gh api /user --jq '.login' # Field from response gh api /repos/owner/repo --jq '.stargazers_count' # GitHub Enterprise gh api /user --hostname enterprise.internal # GraphQL query gh api graphql \ -f query=' { viewer { login repositories(first: 5) { nodes { name } } } }' ``` ## Rulesets (gh ruleset) ```bash # List rulesets gh ruleset list # View ruleset gh ruleset view 123 # Check ruleset gh ruleset check --branch feature # Check specific repository gh ruleset check --repo owner/repo --branch main ``` ## Attestations (gh attestation) ```bash # Download attestation gh attestation download owner/repo \ --artifact-id 123456 # Verify attestation gh attestation verify owner/repo # Get trusted root gh attestation trusted-root ``` ## Completion (gh completion) ```bash # Generate shell completion gh completion -s bash > ~/.gh-complete.bash gh completion -s zsh > ~/.gh-complete.zsh gh completion -s fish > ~/.gh-complete.fish gh completion -s powershell > ~/.gh-complete.ps1 # Shell-specific instructions gh completion --shell=bash gh completion --shell=zsh ``` ## Preview (gh preview) ```bash # List preview features gh preview # Run preview script gh preview prompter ``` ## Agent Tasks (gh agent-task) ```bash # List agent tasks gh agent-task list # View agent task gh agent-task view 123 # Create agent task gh agent-task create --description "My task" ``` ## Global Flags | Flag | Description | | -------------------------- | -------------------------------------- | | `--help` / `-h` | Show help for command | | `--version` | Show gh version | | `--repo [HOST/]OWNER/REPO` | Select another repository | | `--hostname HOST` | GitHub hostname | | `--jq EXPRESSION` | Filter JSON output | | `--json FIELDS` | Output JSON with specified fields | | `--template STRING` | Format JSON using Go template | | `--web` | Open in browser | | `--paginate` | Make additional API calls | | `--verbose` | Show verbose output | | `--debug` | Show debug output | | `--timeout SECONDS` | Maximum API request duration | | `--cache CACHE` | Cache control (default, force, bypass) | ## Output Formatting ### JSON Output ```bash # Basic JSON gh repo view --json name,description # Nested fields gh repo view --json owner,name --jq '.owner.login + "/" + .name' # Array operations gh pr list --json number,title --jq '.[] | select(.number > 100)' # Complex queries gh issue list --json number,title,labels \ --jq '.[] | {number, title: .title, tags: [.labels[].name]}' ``` ### Template Output ```bash # Custom template gh repo view \ --template '{{.name}}: {{.description}}' # Multiline template gh pr view 123 \ --template 'Title: {{.title}} Author: {{.author.login}} State: {{.state}} ' ``` ## Common Workflows ### Create PR from Issue ```bash # Create branch from issue gh issue develop 123 --branch feature/issue-123 # Make changes, commit, push git add . git commit -m "Fix issue #123" git push # Create PR linking to issue gh pr create --title "Fix #123" --body "Closes #123" ``` ### Bulk Operations ```bash # Close multiple issues gh issue list --search "label:stale" \ --json number \ --jq '.[].number' | \ xargs -I {} gh issue close {} --comment "Closing as stale" # Add label to multiple PRs gh pr list --search "review:required" \ --json number \ --jq '.[].number' | \ xargs -I {} gh pr edit {} --add-label needs-review ``` ### Repository Setup Workflow ```bash # Create repository with initial setup gh repo create my-project --public \ --description "My awesome project" \ --clone \ --gitignore python \ --license mit cd my-project # Set up branches git checkout -b develop git push -u origin develop # Create labels gh label create bug --color "d73a4a" --description "Bug report" gh label create enhancement --color "a2eeef" --description "Feature request" gh label create documentation --color "0075ca" --description "Documentation" ``` ### CI/CD Workflow ```bash # Run workflow and wait RUN_ID=$(gh workflow run ci.yml --ref main --jq '.databaseId') # Watch the run gh run watch "$RUN_ID" # Download artifacts on completion gh run download "$RUN_ID" --dir ./artifacts ``` ### Fork Sync Workflow ```bash # Fork repository gh repo fork original/repo --clone cd repo # Add upstream remote git remote add upstream https://github.com/original/repo.git # Sync fork gh repo sync # Or manual sync git fetch upstream git checkout main git merge upstream/main git push origin main ``` ## Environment Setup ### Shell Integration ```bash # Add to ~/.bashrc or ~/.zshrc eval "$(gh completion -s bash)" # or zsh/fish # Create useful aliases alias gs='gh status' alias gpr='gh pr view --web' alias gir='gh issue view --web' alias gco='gh pr checkout' ``` ### Git Configuration ```bash # Use gh as credential helper gh auth setup-git # Set gh as default for repo operations git config --global credential.helper 'gh !gh auth setup-git' # Or manually git config --global credential.helper github ``` ## Best Practices 1. **Authentication**: Use environment variables for automation ```bash export GH_TOKEN=$(gh auth token) ``` 2. **Default Repository**: Set default to avoid repetition ```bash gh repo set-default owner/repo ``` 3. **JSON Parsing**: Use jq for complex data extraction ```bash gh pr list --json number,title --jq '.[] | select(.title | contains("fix"))' ``` 4. **Pagination**: Use --paginate for large result sets ```bash gh issue list --state all --paginate ``` 5. **Caching**: Use cache control for frequently accessed data ```bash gh api /user --cache force ``` ## Getting Help ```bash # General help gh --help # Command help gh pr --help gh issue create --help # Help topics gh help formatting gh help environment gh help exit-codes gh help accessibility ``` ## References - Official Manual: https://cli.github.com/manual/ - GitHub Docs: https://docs.github.com/en/github-cli - REST API: https://docs.github.com/en/rest - GraphQL API: https://docs.github.com/en/graphql ================================================ FILE: .agents/skills/postgres/SKILL.md ================================================ --- name: postgres description: PostgreSQL best practices, query optimization, connection troubleshooting, and performance improvement. Load when working with Postgres databases. license: MIT metadata: author: planetscale version: "1.0.0" --- # PlanetScale Postgres ## Generic Postgres | Topic | Reference | Use for | | ---------------------- | ---------------------------------------------------------------- | --------------------------------------------------------- | | Schema Design | [references/schema-design.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/schema-design.md) | Tables, primary keys, data types, foreign keys | | Indexing | [references/indexing.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/indexing.md) | Index types, composite indexes, performance | | Index Optimization | [references/index-optimization.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/index-optimization.md) | Unused/duplicate index queries, index audit | | Partitioning | [references/partitioning.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/partitioning.md) | Large tables, time-series, data retention | | Query Patterns | [references/query-patterns.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/query-patterns.md) | SQL anti-patterns, JOINs, pagination, batch queries | | Optimization Checklist | [references/optimization-checklist.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/optimization-checklist.md) | Pre-optimization audit, cleanup, readiness checks | | MVCC and VACUUM | [references/mvcc-vacuum.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/mvcc-vacuum.md) | Dead tuples, long transactions, xid wraparound prevention | ## Operations and Architecture | Topic | Reference | Use for | | ---------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------------------- | | Process Architecture | [references/process-architecture.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/process-architecture.md) | Multi-process model, connection pooling, auxiliary processes | | Memory Architecture | [references/memory-management-ops.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/memory-management-ops.md) | Shared/private memory layout, OS page cache, OOM prevention | | MVCC Transactions | [references/mvcc-transactions.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/mvcc-transactions.md) | Isolation levels, XID wraparound, serialization errors | | WAL and Checkpoints | [references/wal-operations.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/wal-operations.md) | WAL internals, checkpoint tuning, durability, crash recovery | | Replication | [references/replication.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/replication.md) | Streaming replication, slots, sync commit, failover | | Storage Layout | [references/storage-layout.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/storage-layout.md) | PGDATA structure, TOAST, fillfactor, tablespaces, disk mgmt | | Monitoring | [references/monitoring.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/monitoring.md) | pg_stat views, logging, pg_stat_statements, host metrics | | Backup and Recovery | [references/backup-recovery.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/backup-recovery.md) | pg_dump, pg_basebackup, PITR, WAL archiving, backup tools | ## PlanetScale-Specific | Topic | Reference | Use for | | ------------------ | ---------------------------------------------------------------------------- | ----------------------------------------------------- | | Connection Pooling | [references/ps-connection-pooling.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-connection-pooling.md) | PgBouncer, pool sizing, pooled vs direct | | Extensions | [references/ps-extensions.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-extensions.md) | Supported extensions, compatibility | | Connections | [references/ps-connections.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-connections.md) | Connection troubleshooting, drivers, SSL | | Insights | [references/ps-insights.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-insights.md) | Slow queries, MCP server, pscale CLI | | CLI Commands | [references/ps-cli-commands.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-cli-commands.md) | pscale CLI reference, branches, deploy requests, auth | | CLI API Insights | [references/ps-cli-api-insights.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-cli-api-insights.md) | Query insights via `pscale api`, schema analysis | ================================================ FILE: .agents/skills/postgres/references/backup-recovery.md ================================================ --- title: Backup and Recovery description: Logical/physical backups, PITR, WAL archiving, backup tools, and recovery strategies tags: postgres, backup, recovery, pitr, pg_dump, pg_basebackup, wal-archiving, operations --- # Backup and Recovery **FUNDAMENTAL RULE: Backups are useless until you've successfully tested recovery.** ## Logical Backups (pg_dump) Exports as SQL or custom format; portable across PG versions and architectures. Formats: `-Fp` (plain SQL), `-Fc` (custom compressed, selective restore), `-Fd` (directory, parallel with `-j`), `-Ft` (tar, avoid). Use `-Fd -j 4` for large DBs. Restore: `pg_restore -d dbname file.dump`; add `-j` for parallel restore. Selective table restore: `pg_restore -t tablename`. Slow for large DBs; RPO = backup frequency (typically 24h). ## Physical Backups (pg_basebackup) Copies raw PGDATA; same major version and platform required; cross-architecture works if same endianness (e.g., x86_64 ↔ ARM64). Faster for large clusters; includes all databases. Flags: `-Ft -z -P` for compressed tar with progress. Manual alternative: `pg_backup_start()` → copy PGDATA → `pg_backup_stop()` (complex; must write returned `backup_label`). ## PITR (Point-in-Time Recovery) Requires base backup + continuous WAL archiving. Restores to any timestamp, transaction, or named restore point. Without PITR: restore only to backup time (potentially lose hours). With PITR: RPO = minutes. `archive_command` must return 0 ONLY when file is safely stored—premature 0 = data loss risk. `wal_level` must be `replica` or `logical` (not `minimal`). ## WAL Archiving `archive_mode=on`, `archive_command='test ! -f /archive/%f && cp %p /archive/%f'`. **Test archive command as postgres user** (not root) since permission issues are common. Monitor `pg_stat_archiver` for `failed_count`, `last_archived_time`. Archive failures prevent WAL recycling → disk fills. ## Tool Comparison | Tool | Use case | |------|----------| | pg_dump | Small DBs, migrations, selective restore | | pg_basebackup | Basic PITR, built-in | | pgBackRest | Production—parallel, incremental, S3/GCS/Azure, retention | | Barman | Enterprise PITR, retention policies | | WAL-G | Cloud-native, S3/GCS/Azure | ## RPO/RTO Logical only: RPO = backup interval (hours); RTO = hours. PITR: RPO = minutes; RTO = hours. Synchronous replication: RPO = 0; RTO = seconds to minutes (failover). ## Operational Rules - Verify integrity with `pg_verifybackup` (PG 13+) - Test recovery / PITR regularly - Take backups from standby to avoid impacting primary - Retention: 7 daily, 4 weekly, 12 monthly - Monitor archive growth and backup age - **Never assume backups work without testing** ================================================ FILE: .agents/skills/postgres/references/index-optimization.md ================================================ --- title: Index Optimization Queries description: Index audit queries tags: postgres, indexes, unused-indexes, duplicate-indexes, optimization --- # Index Optimization ## Identify Unused Indexes Query to find unused indexes: ```sql -- indexes with 0 scans (check pg_stat_reset / pg_postmaster_start_time first) SELECT s.schemaname, s.relname AS table_name, s.indexrelname AS index_name, pg_size_pretty(pg_relation_size(s.indexrelid)) AS index_size FROM pg_catalog.pg_stat_user_indexes s JOIN pg_catalog.pg_index i ON s.indexrelid = i.indexrelid WHERE s.idx_scan = 0 AND 0 <> ALL (i.indkey) -- exclude expression indexes AND NOT i.indisunique -- exclude UNIQUE indexes AND NOT EXISTS ( -- exclude constraint-backing indexes SELECT 1 FROM pg_catalog.pg_constraint c WHERE c.conindid = s.indexrelid ) ORDER BY pg_relation_size(s.indexrelid) DESC; ``` ## Indexes Per Table Guidelines - **< 5**: Normal - **5-10**: Monitor (Verify necessity) - **> 10**: Audit required (High write overhead) ```sql SELECT relname AS table, count(*) as index_count FROM pg_stat_user_indexes GROUP BY relname ORDER BY count(*) DESC; ``` ## Identify Unused Indexes Indexes with identical definitions (after normalizing names) on the same table are duplicates: ```sql SELECT schemaname || '.' || tablename AS table, array_agg(indexname) AS duplicate_indexes, pg_size_pretty(sum(pg_relation_size((schemaname || '.' || indexname)::regclass))) AS total_size FROM pg_indexes WHERE schemaname NOT IN ('pg_catalog', 'information_schema') GROUP BY schemaname, tablename, regexp_replace(indexdef, 'INDEX \S+ ON ', 'INDEX ON ') HAVING count(*) > 1; ``` **Always confirm with a human before dropping or removing any indexes identified by the queries above.** Even indexes with 0 scans may be needed for infrequent but critical queries, and stats may have been reset recently. ## Per-table Index Count Guidelines | Index Count | Recommendation | | ----------- | ------------------------------------------- | | <5 | Normal | | 5-10 | Review for unused/duplicates | | >10 | Audit required - significant write overhead | ================================================ FILE: .agents/skills/postgres/references/indexing.md ================================================ --- title: Indexing Best Practices description: Index design guide tags: postgres, indexes, composite, partial, covering, gin, brin --- # Indexing Best Practices ## Core Rules 1. **Always index foreign key columns** — PostgreSQL does not auto-create these 2. **Index columns in WHERE, JOIN, and ORDER BY** clauses 3. **Don't over-index** — each index slows writes and uses storage 4. **Verify with EXPLAIN ANALYZE** — confirm indexes are actually used ## Composite Indexes Put equality columns first, then range/sort columns: ```sql -- WHERE status = 'active' AND created_at > '2026-01-01' CREATE INDEX order_status_created_idx ON order (status, created_at); ``` A composite index on `(a, b)` supports queries on `a` + `b` and `a` alone, but not `b` alone. ## Partial Indexes Reduce index size by filtering to common query patterns. Only use if index size is problematic but the index is needed for performance. ```sql CREATE INDEX order_active_idx ON order (customer_id) WHERE status = 'active'; ``` ## Covering Indexes Consider creating covering indexes for commonly executed query patterns that return only 1 or a small number of columns. ## Index Types | Type | Use Case | Example | | --- | --- | --- | | B-tree (default) | Equality, range, sorting | `WHERE id = 1`, `ORDER BY date` | | GIN | Arrays, JSONB, full-text | `WHERE tags @> ARRAY['x']` | | GiST | Geometric, range types, full-text | PostGIS, `tsrange`, `tsvector` | | BRIN | Large sequential/time-series | Append-only logs, events (requires physical row order correlation) | ```sql CREATE INDEX metadata_idx ON order USING GIN (metadata); -- JSONB CREATE INDEX event_created_idx ON event USING BRIN (created_at); -- time-series ``` ## Guidelines - Name indexes consistently: `{table}_{column}_idx` - Review for unused indexes periodically - **Always confirm with a human before removing or dropping any indexes** — even unused ones may serve a purpose not reflected in recent stats - Use partial indexes for frequently filtered subsets - Use covering indexes on hot read paths ================================================ FILE: .agents/skills/postgres/references/memory-management-ops.md ================================================ --- title: Memory Architecture and OOM Prevention description: PostgreSQL shared/private memory layout, OS page cache interaction, and OOM avoidance strategies tags: postgres, memory, shared_buffers, work_mem, oom, architecture, operations --- # Memory Architecture and OOM Prevention ## Memory Areas - **Shared memory**: `shared_buffers` — main data cache, all processes, requires restart to change. - **Private per backend**: `work_mem` (sorts/hashes/joins, per-operation); `maintenance_work_mem` (VACUUM, CREATE INDEX, ALTER TABLE ADD FOREIGN KEY); `temp_buffers` (8MB default). - **Planner hint only**: `effective_cache_size` is NOT allocated — set to ~50–75% of total RAM. - **Hash multiplier**: `hash_mem_multiplier` (default 2.0) means hash ops use up to 2× `work_mem`. ## Memory Multiplication Danger Maximum potential: `work_mem × operations_per_query × (parallel_workers + 1) × connections` (leader participates by default via `parallel_leader_participation = on`; hash operations use up to `hash_mem_multiplier × work_mem`, default 2.0). Example: 128MB work_mem, 3 ops (2 sorts + 1 hash join), 2 parallel workers, 100 connections → 2 sorts at 128MB = 256MB, 1 hash join at 128MB × 2.0 = 256MB, per process = 512MB, × 3 processes (2 workers + leader) = 1536MB/query, × 100 connections = **~150GB** worst case. This case is rare. Not all queries hit limits at once, but high concurrency + large datasets approach it. This is a common cause of OOM in containerized/Kubernetes deployments. Plan capacity with a 1.5–2× safety margin. ## OS Page Cache (Double Buffering) Data exists in both `shared_buffers` and OS page cache. A miss in shared_buffers can still hit OS cache (avoiding disk I/O). Extremely large shared_buffers can hurt performance: less OS cache, slower startup, heavier checkpoints. Optimal split depends on workload (OLTP vs OLAP). ## OOM Prevention - Implement connection pooling to reduce total backend count. - Reduce `work_mem` globally; use per-session overrides for heavy queries only. - Lower `max_parallel_workers_per_gather` in high-concurrency systems. - Set `statement_timeout` to kill runaway queries. - Monitor: `dmesg -T | grep "killed process"` and `temp_blks_written` in pg_stat_statements. ## Operational Rules - Tune per-session first, global last. - Suspect OOM when memory spikes during high concurrency, dashboards, or large batch jobs. - Increase memory only after confirming spill behavior (`temp_blks_written > 0`). - `maintenance_work_mem` can be set much higher (1–2GB) — fewer processes use it. Cap autovacuum with `autovacuum_work_mem` to avoid `autovacuum_max_workers × maintenance_work_mem` memory spikes. - `shared_buffers` change requires full restart; `work_mem` is per-session changeable. ================================================ FILE: .agents/skills/postgres/references/monitoring.md ================================================ --- title: Monitoring description: Essential PostgreSQL monitoring views, pg_stat_statements, logging, host metrics, and statistics management tags: postgres, monitoring, pg_stat_statements, logging, pgbadger, metrics, operations --- # Monitoring ## Essential Views - **pg_stat_activity**: First stop when something is wrong — running queries, states, wait events, locks. - **pg_stat_statements**: Execution stats for all SQL. Requires `shared_preload_libraries = 'pg_stat_statements'` and `CREATE EXTENSION pg_stat_statements`. - **pg_stat_database**: Cache hit ratio, temp files, deadlocks, connections per database. - **pg_stat_user_tables**: `seq_scan` vs `idx_scan`, dead tuples, last vacuum/analyze times. - **pg_stat_user_indexes**: Find unused indexes (`idx_scan = 0` with large size). - **pg_stat_bgwriter**: `buffers_clean`, `maxwritten_clean`, `buffers_alloc`. Pre-PG 17 also had `buffers_checkpoint`, `buffers_backend` (high = backends bypassing bgwriter). PG 17+ moved checkpoint stats to `pg_stat_checkpointer`. - **pg_stat_checkpointer** (PG 17+): Checkpoint frequency (`num_timed`, `num_requested`), write/sync time. ## Key Queries ```sql -- Slow queries (with cache hit ratio) SELECT query, calls, mean_exec_time, 100.0 * shared_blks_hit / nullif(shared_blks_hit + shared_blks_read, 0) AS cache_hit_pct FROM pg_stat_statements ORDER BY mean_exec_time DESC LIMIT 10; -- Connection counts / states SELECT state, count(*) FROM pg_stat_activity GROUP BY state; -- Dead tuples (vacuum candidates) SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC; -- last_autovacuum = means autovacuum has not run on this table ``` Blocking: use `pg_blocking_pids(pid)` with `pg_stat_activity` to find blocked and blocking sessions. ## Logging — First Line of Defense PostgreSQL is extremely vocal about problems. **Always check logs first**: `tail -f /var/log/postgresql/postgresql-*.log`. Key settings: `log_min_duration_statement` (OLTP: 1–3s, analytics: 30–60s, dev: 100–500ms). Enable `log_checkpoints=on`, `log_connections=on`, `log_disconnections=on`, `log_lock_waits=on`, `log_temp_files=0`. Use CSV log format for pgBadger analysis; pgBadger generates HTML reports with query stats and performance graphs. ## pg_activity Interactive top-like tool (pip install pg_activity). Run on DB host for OS metrics alongside PG metrics. Combines `pg_stat_activity` with CPU/memory/I/O context. ## Host Metrics — Critical PostgreSQL cannot report these. **Monitor them yourself:** - **CPU**: Steal time >10% in VMs bad; load average > core count; context switches >100k/sec. - **Memory**: Any swap = performance degradation. Check `dmesg` for OOM kills. - **Disk I/O**: `iostat -x` — `%util=100%` means saturated; `await` >10ms = high latency. - **Disk space**: >90% critical (VACUUM fails, writes fail). Check inode usage too. - **Network**: Packet loss >0% = problems; high retransmits = instability. ## Statistics Management Stats accumulate since last reset or restart; check `stats_reset` timestamp. `pg_stat_statements_reset()` clears query stats; `pg_stat_reset()` clears database stats. Reset after major maintenance, config changes, or perf testing — not routinely. Prefer snapshotting stats to external monitoring (Prometheus, Datadog) over resetting. **Always confirm with a human before resetting statistics** — resetting destroys historical performance baselines and can make it harder to identify unused indexes or regressions. ================================================ FILE: .agents/skills/postgres/references/mvcc-transactions.md ================================================ --- title: MVCC Transactions and Concurrency description: Transaction isolation levels, XID wraparound prevention, serialization errors, and long-transaction impact tags: postgres, mvcc, transactions, isolation, xid-wraparound, concurrency, serialization --- # MVCC Transactions and Concurrency ## Transaction Isolation Levels - **READ UNCOMMITTED** — treated as READ COMMITTED in PostgreSQL; no dirty reads ever. - **READ COMMITTED** (default): new snapshot per statement; can see different data within same tx. - **REPEATABLE READ**: snapshot at first query; can cause serialization errors on write conflicts. - **SERIALIZABLE**: strongest; transactions appear serial; requires retry logic in app code. Readers never block writers; writers never block readers (only writer-writer conflicts on same row). No lock escalation — row locks never degrade to table locks. ## XID Wraparound 32-bit transaction IDs wrap at ~2 billion (2^31). `VACUUM FREEZE` replaces old XIDs with FrozenXID (value 2, always visible). Without freeze: after wraparound, old rows appear "in the future" and become **invisible**. Data physically exists but is invisible to all queries — looks like total data loss. PostgreSQL emergency shutdown at 2B XIDs to prevent this. XID wraparound should be avoided at all cost. Warning messages start at ~1.4B XIDs; shutdown at 2B. Recovery requires single-user mode VACUUM — can take hours to days on large DBs. **Never disable autovacuum** — it's your protection against wraparound. ## XID Age Monitoring ```sql SELECT datname, age(datfrozenxid), ROUND(100.0 * age(datfrozenxid) / 2147483648, 2) AS pct FROM pg_database ORDER BY age(datfrozenxid) DESC; ``` ## Long Transaction Impact A single long-running transaction blocks VACUUM from removing dead tuples across the **entire database**. Causes table bloat, increased disk, slower queries, cache pollution. `idle_in_transaction` connections are the #1 operational MVCC issue. Set `idle_in_transaction_session_timeout` (30s–5min). Dead tuples waste I/O on seq scans and cause useless heap lookups from indexes. ## Serialization Errors Apps **must** handle "could not serialize access" with retry logic. More common in REPEATABLE READ and SERIALIZABLE. Smaller, faster transactions reduce conflict frequency. ================================================ FILE: .agents/skills/postgres/references/mvcc-vacuum.md ================================================ --- title: MVCC and VACUUM description: MVCC internals, VACUUM/autovacuum tuning, and bloat prevention tags: postgres, mvcc, vacuum, autovacuum, xid, bloat, dead-tuples --- # MVCC and VACUUM ## MVCC Every `UPDATE` creates a new tuple and marks the old one dead; `DELETE` marks tuples dead. Dead tuples accumulate until `VACUUM` reclaims space. Each transaction gets a 32-bit XID (2^32 ≈ 4B values, but modular comparison means the effective danger zone is 2^31 ≈ 2B). VACUUM must freeze old XIDs to prevent wraparound. ## VACUUM vs VACUUM FULL `VACUUM` is non-blocking (ShareUpdateExclusive lock) and marks dead space reusable. `VACUUM FULL` rewrites the table and requires an AccessExclusive lock — use only as a last resort. For online bloat reduction prefer `pg_squeeze` or `pg_repack`. ## Autovacuum Tuning Triggers when dead tuples > `Min(autovacuum_vacuum_max_threshold, autovacuum_vacuum_threshold + autovacuum_vacuum_scale_factor * reltuples)`. `autovacuum_vacuum_max_threshold` defaults to 100M (PG 18+), capping the threshold for very large tables. Also triggers on inserts exceeding `autovacuum_vacuum_insert_threshold + autovacuum_vacuum_insert_scale_factor * reltuples * pct_not_frozen` (ensures insert-only tables get frozen; PG 13+). For large/hot tables, set per-table overrides: - `autovacuum_vacuum_scale_factor` — default 0.2; lower to 0.01–0.05 for large tables. - `autovacuum_vacuum_cost_delay` — default 2 ms; set to 0 on fast storage. - `autovacuum_vacuum_cost_limit` — default -1 (uses `vacuum_cost_limit`, effectively 200); raise to 1000–2000 on fast storage. - `autovacuum_freeze_max_age` — default 200M; triggers anti-wraparound vacuum. - `vacuum_failsafe_age` — default 1.6B; last-resort mode (PG 14+) that disables throttling and skips index vacuuming when wraparound is imminent. ## Key Monitoring Queries Dead tuples: `SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC;` XID age: `SELECT datname, age(datfrozenxid) AS xid_age FROM pg_database ORDER BY xid_age DESC;` Long transactions: `SELECT pid, state, now() - xact_start AS tx_age FROM pg_stat_activity WHERE xact_start IS NOT NULL ORDER BY xact_start;` ## Best Practices - Keep transactions short; set `idle_in_transaction_session_timeout` (30s–5min). - Alert when `age(datfrozenxid)` exceeds 40–50% of wraparound (~800M–1B). - Tune autovacuum per-table for write-heavy tables; don't change global defaults first. - Fix application transaction scope before adjusting vacuum parameters. - Never disable autovacuum globally. ================================================ FILE: .agents/skills/postgres/references/optimization-checklist.md ================================================ --- title: Database Optimization Checklist description: Optimize checklist tags: postgres, optimization, indexes, partitioning, maintenance --- # Optimization Checklist When optimizing performance, check the following: - Look for unused indexes (0 scans; exclude unique/primary indexes and verify stats age first) - Look for duplicate indexes - Archive audit/log tables >10GB - Review tables >500GB for partitioning (>100GB for time-series/logs) - Verify all extensions are supported - Check for circular foreign key dependencies - Consider alternatives to UUID primary keys for large tables - Configure connection pooling for OLTP workloads - **Always confirm with a human before removing any indexes, dropping partitions, archiving tables, or performing other destructive actions** ================================================ FILE: .agents/skills/postgres/references/partitioning.md ================================================ --- title: Table Partitioning Guide description: Partition guide tags: postgres, partitioning, range, list, pg_partman, data-retention --- # Table Partitioning Plan partitioning upfront for tables expected to grow large. Retrofitting later requires a migration. ## When to Partition Partitioning benefits maintenance (vacuum, index builds) and data retention more than pure query speed. | Table Type | Size Threshold | Row Threshold | | --- | --- | --- | | General tables | >100 GB (or >RAM) | >20M rows | | Time-series / logs | >50 GB | >10M rows | Use the lower thresholds for append-heavy, time-ordered data with retention needs (logs, events, audit trails, metrics). ## Range Partitioning (Most Common) ```sql -- EXAMPLE CREATE TABLE event ( id BIGINT GENERATED ALWAYS AS IDENTITY, event_type TEXT NOT NULL, payload JSONB, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), PRIMARY KEY (id, created_at) -- Partition key MUST be part of PK ) PARTITION BY RANGE (created_at); CREATE TABLE event_2026_01 PARTITION OF event FOR VALUES FROM ('2026-01-01') TO ('2026-02-01'); CREATE TABLE event_2026_02 PARTITION OF event FOR VALUES FROM ('2026-02-01') TO ('2026-03-01'); ``` ## List Partitioning Useful for partitioning by region, tenant, or category: ```sql -- EXAMPLE CREATE TABLE order ( id BIGINT GENERATED ALWAYS AS IDENTITY, region TEXT NOT NULL, total NUMERIC(10,2), PRIMARY KEY (id, region) -- Partition key MUST be part of PK ) PARTITION BY LIST (region); CREATE TABLE order_us PARTITION OF order FOR VALUES IN ('us'); CREATE TABLE order_eu PARTITION OF order FOR VALUES IN ('eu'); CREATE TABLE order_default PARTITION OF order DEFAULT; -- catches unmatched values ``` ## Partition Management - Use `pg_partman` (extension) to automate partition creation and cleanup. - Use `DETACH PARTITION` to remove a partition while retaining it as a standalone table (e.g., for archiving). - Use `DETACH PARTITION ... CONCURRENTLY` (PG 14+) to avoid `ACCESS EXCLUSIVE` locks on the parent table. - Drop old partitions for data retention instead of `DELETE` to avoid vacuum overhead and bloat. - Create future partitions ahead of time to avoid insert failures. - **Always confirm with a human before detaching or dropping partitions.** These are destructive actions — detaching removes data from the partitioned table, and dropping permanently deletes the data. ```sql -- DESTRUCTIVE: confirm with a human before executing ALTER TABLE event DETACH PARTITION event_2025_01 CONCURRENTLY; DROP TABLE event_2025_01; ``` ## Guidelines & Limitations - **Primary Keys**: Partition key columns MUST be included in the `PRIMARY KEY` and any `UNIQUE` constraints. - **Global Uniqueness**: Global unique constraints on non-partition columns are NOT supported. - **Indexes**: Indexes defined on the parent are automatically created on all partitions (and future ones). - **Pruning**: Ensure queries filter by the partition key to enable "partition pruning" (skipping unrelated partitions). ================================================ FILE: .agents/skills/postgres/references/process-architecture.md ================================================ --- title: Process Architecture description: PostgreSQL multi-process model, connection management, and auxiliary processes tags: postgres, processes, connections, pooling, memory, operations --- # Process Architecture PostgreSQL uses a **multi-process** model, not multi-threaded: one OS process per client connection. The postmaster is the parent; it spawns backend processes per connection. Each backend has some private memory (`work_mem`, temp buffers). 1000 connections = 1000 processes (~5–10MB base + query memory each). There is also a large buffer shared amongst all. ## Auxiliary Processes WAL Writer, Background Writer, Checkpointer, Autovacuum Launcher/Workers, Archiver, WAL Summarizer (PG 17+). These run alongside backends and are not spawned per connection. ## Memory Risk `work_mem` is per-operation, not per-query. Estimate: `work_mem × operations_per_query × parallel_workers × connections` can grow very large at high concurrency. Scale connections and parallelism before raising `work_mem`. ## Connection Pooling (Critical) Each connection = OS process (fork overhead, context switching, memory). PgBouncer can multiplex many app connections to fewer DB connections. Typical: 1000 app connections → pooler → 20–50 backends. Implement pooling before raising `max_connections`; `max_connections` requires a full restart to change (default 100). Note: `superuser_reserved_connections` (default 3) reserves slots for emergency superuser access, so non-superusers are rejected before `max_connections` is fully reached. ## Monitoring ```sql SELECT state, count(*) FROM pg_stat_activity WHERE backend_type = 'client backend' GROUP BY state; ``` ```sql -- Show used and free connection slots SELECT count(*) AS used, max(max_conn) - count(*) AS free FROM pg_stat_activity, (SELECT setting::int AS max_conn FROM pg_settings WHERE name = 'max_connections') s WHERE backend_type = 'client backend'; ``` Use `pg_activity` for interactive top-like monitoring. Alert at 80% connection usage, critical at 95%. Count by state to find idle-in-transaction leaks — these hold locks and **block VACUUM** from reclaiming dead tuples. ## Common Problems | Problem | Fix | | ------- | --- | | `too many clients already` | Implement pooling; find idle connections; check for connection leaks | | High memory / OOM | Reduce `work_mem`; add pooling; set `statement_timeout` | | Stuck process | `SELECT pg_cancel_backend(pid);` then `SELECT pg_terminate_backend(pid);` — **always confirm with a human before terminating backends**, as this may abort in-flight transactions and cause data issues for the application | Prefer pooling + conservative `max_connections` over raising limits reactively. ================================================ FILE: .agents/skills/postgres/references/ps-cli-api-insights.md ================================================ --- title: CLI Query Insights API description: CLI insights usage tags: postgres, planetscale, cli, insights, query-patterns, api --- # Query Insights via pscale CLI Analyze slow queries and missing indexes using `pscale api`. Endpoints may change—see https://planetscale.com/docs/api/reference/getting-started-with-planetscale-api for current API docs. ## Using pscale api The `pscale api` command makes authenticated API calls using your current login or service token (see [ps-cli-commands.md](ps-cli-commands.md#service-token-cicd) for auth setup). No need to manage auth headers manually. ```bash pscale api "" [--method POST] [--field key=value] [--org ] ``` ## Query Patterns Reports ```bash # Create a new report pscale api "organizations/{org}/databases/{db}/branches/{branch}/query-patterns-reports" \ --method POST --org my-org # Check status (poll until state=complete) pscale api "organizations/{org}/databases/{db}/branches/{branch}/query-patterns-reports/{id}/status" # Download completed report pscale api "organizations/{org}/databases/{db}/branches/{branch}/query-patterns-reports/{id}" # List all reports pscale api "organizations/{org}/databases/{db}/branches/{branch}/query-patterns-reports" ``` ## Schema Analysis ```bash # Get branch schema pscale api "organizations/{org}/databases/{db}/branches/{branch}/schema" # Lint schema for issues pscale api "organizations/{org}/databases/{db}/branches/{branch}/schema/lint" ``` ## What to Look For | Metric | Indicates | Action | | -------------------------------- | --------------------- | ------------------------------- | | High `rows_read / rows_returned` | Missing or poor index | Add index on WHERE/JOIN columns | | High `total_time_s` | Heavy query | Optimize or cache | | High `count` with same pattern | N+1 queries | Batch or eager-load | | `indexed: false` | Full table scan | Add index | ================================================ FILE: .agents/skills/postgres/references/ps-cli-commands.md ================================================ --- title: PlanetScale CLI Reference description: CLI command guide tags: planetscale, cli, branches, deploy-requests, authentication --- # pscale CLI Commands Full CLI reference: https://planetscale.com/docs/cli. Use `pscale --help` for subcommands and flags. ## Authentication ```bash pscale auth login # Opens browser pscale auth logout pscale org list pscale org switch ``` ### Service Token (CI/CD) ```bash # Create and configure pscale service-token create pscale service-token add-access read_branch --database # Use in CI/CD export PLANETSCALE_SERVICE_TOKEN_ID="" export PLANETSCALE_SERVICE_TOKEN="" ``` ## Core Commands ```bash # Databases pscale database list pscale database create # Branches pscale branch list pscale branch create [--from ] pscale branch delete # DESTRUCTIVE — always confirm with a human first pscale branch schema # Deploy requests (schema changes) — Vitess only pscale deploy-request create pscale deploy-request list pscale deploy-request deploy # Connect pscale shell # Opens psql (Postgres) or mysql (Vitess) pscale connect # Proxy for GUI tools (secure tunnel) — Vitess only # Credentials pscale role create # Postgres pscale password create # Vitess # Other pscale ping # Check latency to regions pscale region list # Available regions pscale backup list pscale backup create ``` ## Useful Flags ```bash --format json # Output as JSON (also: csv, human) --org # Specify organization --debug # Debug output ``` For API calls via CLI, see [ps-cli-api-insights.md](ps-cli-api-insights.md). ================================================ FILE: .agents/skills/postgres/references/ps-connection-pooling.md ================================================ --- title: PgBouncer Connection Pooling description: Pooling setup guide tags: postgres, pgbouncer, connection-pooling, performance, transactions --- # Connection Pooling with PgBouncer PlanetScale provides PgBouncer for connection pooling. Connect on port `6432` instead of `5432`. ## When to Use PgBouncer (Port 6432) All OLTP application workloads: web apps, APIs, high-concurrency read/write operations. ## When to Use Direct Connections (Port 5432) - Schema changes (DDL) - Analytics, reporting, batch processing - Session-specific features (temp tables, session variables) - ETL, data streaming, `pg_dump` - Long-running admin transactions ## PgBouncer Types PlanetScale offers three PgBouncer options. All use port `6432`. | Type | Runs On | Routes To | Key Trait | | ---- | ------- | --------- | --------- | | **Local** | Same node as primary | Primary only | Included with every database; no replica routing | | **Dedicated Primary** | Separate node | Primary | Connections persist through resizes, upgrades, and most failovers | | **Dedicated Replica** | Separate node | Replicas | Read-only traffic; supports AZ affinity for lower latency | - **Local PgBouncer** — use same credentials as direct, just change port to `6432`. Always routes to primary regardless of username. - **Dedicated Primary** — runs off-server for improved HA. Use for production OLTP write traffic. - **Dedicated Replica** — runs off-server for read-heavy workloads. Supports AZ affinity to prefer same-zone replicas. Multiple can be created for capacity or per-app isolation. To connect to a dedicated PgBouncer, append `|pgbouncer-name` to the username (e.g., `postgres.xxx|write-pool` or `postgres.xxx|read-bouncer`). ## Transaction Pooling Limitations PlanetScale PgBouncer uses **transaction pooling mode**. These features are unavailable: - Prepared statements that persist across transactions - Temporary tables - `LISTEN`/`NOTIFY` - Session-level advisory locks - `SET` commands persisting beyond a transaction ## Recommended Patterns - Size pools from observed concurrency, query memory behavior, and connection limits. - Keep pooled app traffic on `6432` and reserve direct connections for DDL/admin/long-running jobs. ## Avoid Patterns - Avoid setting pool size with only `CPU_cores * N` while ignoring query-memory amplification. - Avoid running session-dependent workflows through transaction pooling. ## Connecting ```bash # Local PgBouncer (same credentials, port 6432) psql 'host=xxx.horizon.psdb.cloud port=6432 user=postgres.xxx password=pscale_pw_xxx dbname=mydb sslnegotiation=direct sslmode=verify-full sslrootcert=system' # Dedicated primary PgBouncer (append |pgbouncer-name to user) psql 'host=xxx.horizon.psdb.cloud port=6432 user=postgres.xxx|write-pool password=pscale_pw_xxx dbname=mydb sslnegotiation=direct sslmode=verify-full sslrootcert=system' # Dedicated replica PgBouncer (append |pgbouncer-name to user) psql 'host=xxx.horizon.psdb.cloud port=6432 user=postgres.xxx|read-bouncer password=pscale_pw_xxx dbname=mydb sslnegotiation=direct sslmode=verify-full sslrootcert=system' ``` Docs: https://planetscale.com/docs/postgres/connecting/pgbouncer ================================================ FILE: .agents/skills/postgres/references/ps-connections.md ================================================ --- title: PlanetScale Postgres Connections description: Connection guide for PlanetScale Postgres tags: planetscale, postgres, connections, ssl, troubleshooting --- # PlanetScale Postgres Connections Postgres docs: https://planetscale.com/docs/postgres/connecting | Protocol | Standard Port | Pooled Port | SSL | | -------- | ------------- | ----------------------- | -------- | | Postgres | 5432 | 6432 (PgBouncer) | Required | Credentials (roles) are branch-specific and cannot be recovered after creation. ## Connection String ``` postgresql://:@.horizon.psdb.cloud:5432/?sslmode=verify-full&sslrootcert=system&sslnegotiation=direct ``` Use port **6432** for PgBouncer (applications/OLTP). Use port **5432** for DDL, admin tasks, and migrations. ## Troubleshooting | Error | Fix | | -------------------------------- | --------------------------------------- | | `password authentication failed` | Check role format: `.` | | `too many clients already` | Use PgBouncer (port 6432) | | `SSL connection is required` | Add `sslmode=verify-full&sslrootcert=system` | **Best practices:** - Use the PlanetScale Postgres metrics page to monitor direct and PgBouncer connections - Route OLTP traffic to port 6432 and reserve 5432 for admin/migrations. - Avoid raising `max_connections` reactively instead of pooling. ================================================ FILE: .agents/skills/postgres/references/ps-extensions.md ================================================ --- title: PlanetScale PostgreSQL Extensions description: Extension reference tags: postgres, extensions --- # PostgreSQL Extensions on PlanetScale Only use PlanetScale-supported extensions. For the complete and up-to-date list of available extensions, see: https://planetscale.com/docs/postgres/extensions Do not rely on hard-coded extension lists — always check the documentation above for current availability. ## Enabling Extensions Some extensions must first be **enabled in the PlanetScale Dashboard** (Clusters > Extensions) before they can be created in SQL. This often requires a database restart. Once enabled in the dashboard, create the extension in SQL: ```sql CREATE EXTENSION IF NOT EXISTS ; ``` ## Recommended Patterns - Always check the [PlanetScale extensions docs](https://planetscale.com/docs/postgres/extensions) before assuming an extension is available. - Verify extension availability in PlanetScale configuration and docs before schema design depends on it. - Enable `pg_stat_statements` early for baseline query telemetry. ================================================ FILE: .agents/skills/postgres/references/ps-insights.md ================================================ --- title: PlanetScale Query Insights description: Query insights guide tags: postgres, planetscale, insights, monitoring, optimization --- # PlanetScale Insights ## Fetch current documentation first Prefer retrieval over pre-training knowledge. Docs: https://planetscale.com/docs ## MCP Server (Preferred) When the PlanetScale MCP server is configured in your environment, prefer it over CLI. Key tools: - `planetscale_get_branch_schema` — Get schema for a branch - `planetscale_execute_read_query` — Run SELECT, SHOW, DESCRIBE, EXPLAIN - `planetscale_get_insights` — Query performance insights - `planetscale_list_schema_recommendations` — Index and schema suggestions - `planetscale_search_documentation` — Search PlanetScale docs MCP setup: https://planetscale.com/docs/connect/mcp The MCP server is the ideal way to interact with insights from an AI agent. If not installed, prompt the user to install it to make the agent more effective. ## Query Insights (CLI) Generating reports via CLI is a multi-step process (create → wait → download). See [ps-cli-api-insights.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/ps-cli-api-insights.md) for how to use. What to look for: - High `rows_read / rows_returned` ratio → missing index - High `total_time_s` → optimization target ## Insights UI (Dashboard) In the [PlanetScale dashboard](https://app.planetscale.com/), select your database and click **Insights**. - **Filtering** — Pick a branch, choose primary or replica, and scroll through the last 7 days. Click-and-drag on graphs to zoom into a time window. - **Graphs** — Four tabs: Query latency (p50/p95/p99/p99.9), Queries per second, Rows read/s, and Rows written/s. - **Queries table** — All queries in the selected timeframe, normalized into patterns. Sortable and filterable by SQL, schema, table, latency, index usage, and more. Customizable columns (count, total time, latency percentiles, rows read/returned/affected, CPU/IO time, cache hit ratio, etc.). Enable sparklines for inline trend graphs. Orange icons flag full table scans. - **Query deep dive** — Click any query to see per-pattern graphs, summary stats, index usage breakdown, and a table of notable executions (>1 s, >10k rows read, or errors). Use "Summarize query" for an LLM-generated plain-English description. - **Anomalies tab** — Flags periods with elevated slow-running queries and surfaces the responsible patterns. - **Errors tab** — Surfaces queries that produced errors. - **pginsights settings** — `pginsights.raw_queries` enables full query text collection for notable queries; `pginsights.normalize_schema_names` groups identical patterns across schemas (useful for schema-per-tenant designs). Both configurable in the Extensions tab on the Clusters page. More: [PlanetScale Insights docs](https://planetscale.com/docs/postgres/monitoring/query-insights) ## Optimization Checklist - Remove unused indexes (0 scans) - Remove duplicate indexes - Archive audit/log tables >10 GB - Review tables >100 GB for partitioning **Always confirm with a human before removing indexes, dropping tables/partitions, or archiving data.** These are destructive actions that cannot be easily undone. More: [optimization-checklist.md](https://raw.githubusercontent.com/planetscale/database-skills/main/skills/postgres/references/optimization-checklist.md) ================================================ FILE: .agents/skills/postgres/references/query-patterns.md ================================================ --- title: SQL Query Patterns description: Common SQL anti-patterns and optimized alternatives tags: postgres, sql, query-optimization, n-plus-one, pagination --- # SQL Query Patterns ## Query Structure **SELECT specific columns** — avoids fetching unnecessary data and enables covering indexes: ```sql -- Bad: SELECT * FROM user WHERE status = 'active'; -- Good: SELECT id, name, email FROM user WHERE status = 'active'; ``` **Subqueries → JOINs** — correlated subqueries re-execute per row: ```sql -- Bad SELECT id, (SELECT COUNT(*) FROM order WHERE order.user_id = user.id) FROM user; -- Good SELECT u.id, COUNT(o.id) FROM user u LEFT JOIN order o ON o.user_id = u.id GROUP BY u.id; ``` **Always LIMIT unbounded queries** — prevent runaway result sets: ```sql SELECT id, message FROM log WHERE level = 'error' ORDER BY created_at DESC LIMIT 100; ``` **Avoid functions on indexed columns (SARGable)** — functions prevent index usage unless a functional index exists: ```sql -- Bad: Full table scan SELECT * FROM user WHERE date_trunc('day', created_at) = '2023-01-01'; -- Good: Index scan SELECT * FROM user WHERE created_at >= '2023-01-01' AND created_at < '2023-01-02'; ``` ## N+1 Detection **Queries inside loops → batch with ANY/IN:** ```python # Bad for uid in user_ids: cursor.execute("SELECT name FROM user WHERE id = %s", (uid,)) # Good (Postgres specific) cursor.execute("SELECT id, name FROM user WHERE id = ANY(%s)", (list(user_ids),)) # Good (Standard SQL) # cursor.execute("SELECT id, name FROM user WHERE id IN %s", (tuple(user_ids),)) ``` **ORM lazy loading → eager loading:** ```python # Bad: N+1 — each iteration fires a query for user in User.query.all(): print(user.posts) # Good users = User.query.options(joinedload(User.posts)).all() ``` ## Query Rewrites **UNION → UNION ALL** — skip deduplication when duplicates are impossible or acceptable. **IN subquery → EXISTS** — EXISTS short-circuits on first match: ```sql SELECT id, name FROM user u WHERE EXISTS (SELECT 1 FROM order o WHERE o.user_id = u.id AND o.total > 100); ``` **OFFSET → cursor pagination** — OFFSET scans and discards rows, degrading at depth: ```sql -- Bad: OFFSET 10000 scans 10020 rows SELECT id, title FROM article ORDER BY created_at DESC LIMIT 20 OFFSET 10000; -- Good: cursor-based (requires index on (created_at DESC, id DESC)) SELECT id, title FROM article WHERE (created_at, id) < ('2025-06-15T12:00:00Z', 987654) ORDER BY created_at DESC, id DESC LIMIT 20; ``` ================================================ FILE: .agents/skills/postgres/references/replication.md ================================================ --- title: Replication description: Streaming replication, replication slots, synchronous commit levels, failover, and standby management tags: postgres, replication, streaming, slots, synchronous, failover, standby, operations --- # Replication ## Streaming Replication for followers Use physical (byte-for-byte) replication via WAL stream from primary to standbys. Standbys are read-only (hot standby); same major PG version and architecture required (same minor recommended). Without replication slots, the primary may recycle WAL before the standby receives it → standby needs full resync via `pg_basebackup`. Use replication slots to guarantee WAL retention for specific standbys. ## Replication Slots Postgres supports Physical slots (streaming) and logical slots (logical replication). Slots prevent WAL deletion even if standby is offline — can exhaust `pg_wal/` disk. Use `max_slot_wal_keep_size` to cap retained WAL per slot. Use `idle_replication_slot_timeout` (PG 17+) to auto-invalidate idle slots. `wal_keep_size` is a simpler alternative to slots for WAL retention. Drop inactive slots immediately to prevent disk exhaustion. Slot lag (MB behind): `SELECT slot_name, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn)/1024/1024 AS mb_behind FROM pg_replication_slots;` Drop inactive slot: `SELECT pg_drop_replication_slot('slot_name');` **Always confirm with a human before dropping replication slots.** Dropping an active or needed slot can cause downstream issues. ## Synchronous Commit Levels | Level | Behavior | Use Case | |-------|----------|----------| | `off` | Returns immediately, no wait | Non-critical writes; risks losing ~600ms of commits on crash (no inconsistency) | | `local` | Waits for local WAL fsync only | Local durability only; no standby wait | | `remote_write` | Waits for standby OS buffer | Data loss on standby OS crash | | `on` | Waits for standby WAL to disk when `synchronous_standby_names` is set; otherwise same as `local` | **Default. This level or higher recommended for HA** | | `remote_apply` | Waits for standby to apply WAL | Strongest; read-your-writes | Configure with `synchronous_standby_names`. Use `ANY N` for quorum or `FIRST N` for priority-based sync. ## Quorum and Failure `FIRST 2 (s1, s2, s3)` is priority-based: waits for the 2 highest-priority connected standbys (s1+s2; s3 takes over only if one disconnects). `ANY 2 (s1, s2, s3)` is quorum-based: waits for any 2. With either, if only 1 is healthy, commits hang. Provision at least N+1 standbys: need 2 confirmations → provision 3. PostgreSQL never commits unless required standbys confirm — no inconsistency, but clients may timeout. ## Failover `pg_ctl promote` or `SELECT pg_promote()` (SQL function, PG 12+) converts standby to primary. One-way: promoted standby cannot rejoin as standby without rebuild. `pg_rewind` can resync old primary to new primary (requires `wal_log_hints=on` or data checksums) — faster than full rebuild. After promotion: update connection strings, rebuild old primary as standby, reconfigure other standbys. ## Monitoring On the primary, query `pg_stat_replication` for each connected standby's `state` (`streaming` = healthy, `catchup` = behind), `sync_state` (`sync`/`async`), and LSN positions (`sent_lsn`, `write_lsn`, `flush_lsn`, `replay_lsn`) to compute lag. On standbys, `pg_stat_wal_receiver` shows the receiver process status and `flushed_lsn`; compare `pg_last_wal_receive_lsn()` vs `pg_last_wal_replay_lsn()` for local replay lag. Replication lag (MB): `SELECT application_name, pg_wal_lsn_diff(pg_current_wal_lsn(), replay_lsn)/1024/1024 AS lag_mb FROM pg_stat_replication;` Enable `wal_compression` (`pglz`, `lz4`, or `zstd`) to compress full page images in WAL (not all WAL data) — reduces WAL size for bandwidth-limited replication. ================================================ FILE: .agents/skills/postgres/references/schema-design.md ================================================ --- title: PostgreSQL Schema Design description: Schema design guide tags: postgres, schema, primary-keys, data-types, foreign-keys, naming --- # Schema Design ## Primary Keys Prefer `BIGINT GENERATED ALWAYS AS IDENTITY`. Avoid random UUIDs (UUIDv4) as primary keys; use `uuidv7()` when you need UUIDs. ```sql CREATE TABLE user ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, email TEXT NOT NULL UNIQUE ); ``` Random UUID PKs (v4) can cause index fragmentation; UUIDs are also larger (16 vs 8 bytes for BIGINT) and can slow joins. ## Data Types | Use | Avoid | | --- | --- | | `TEXT`, `VARCHAR` | Extension-specific types | | `JSONB` | Custom ENUMs (use CHECK instead) | | `TIMESTAMPTZ` | `TIMESTAMP` without time zone | | `BIGINT`, `INTEGER` | Platform-specific types | Prefer CHECK constraints over ENUM types — they're easier to modify: ```sql CREATE TABLE order ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, status TEXT NOT NULL CHECK (status IN ('pending', 'shipped', 'delivered')) ); ``` ## Foreign Keys - Always index FK columns (PostgreSQL does not auto-create these) - Avoid circular FK dependencies - Suggestion: use `ON DELETE CASCADE` or `ON DELETE SET NULL` explicitly ```sql CREATE TABLE order ( id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, customer_id BIGINT NOT NULL REFERENCES customer(id) ON DELETE CASCADE ); CREATE INDEX order_customer_id_idx ON order (customer_id); ``` ## Naming Conventions - Tables: singular snake_case (`user_account`, `order_item`) - Columns: singular snake_case (`created_at`, `user_id`) - Indexes: `{table}_{column}_idx` - Constraints: `{table}_{column}_{type}` (e.g., `order_status_check`) ## General Guidelines - Add `NOT NULL` to as many columns as possible - Add `created_at TIMESTAMPTZ DEFAULT NOW()` to all tables - Use `BIGINT` for all IDs and foreign keys, even on small tables - Keep tables normalized; denormalize only for proven hot read paths ================================================ FILE: .agents/skills/postgres/references/storage-layout.md ================================================ --- title: Storage Layout and Tablespaces description: PGDATA directory structure, TOAST, fillfactor, tablespaces, and disk management tags: postgres, storage, pgdata, toast, fillfactor, tablespaces, disk, operations --- # Storage Layout and Tablespaces ## PGDATA Structure - **base/** — database files (one subdirectory per database, named by OID) - **global/** — cluster-wide shared catalogs (pg_database, pg_authid, pg_tablespace) - **pg_wal/** — WAL files - **pg_xact/** — transaction commit status "Cluster" in PostgreSQL = single instance with one PGDATA, not an HA cluster. Each table/index = one or more files, split into 1GB segments. Tables have companion **_fsm** (free space map) and **_vm** (visibility map); indexes have **_fsm** only (no _vm), except hash indexes. ## Visibility Map and Free Space Map - **_vm** tracks all-visible pages — VACUUM skips these - **_fsm** tracks free space per page — INSERT uses this to find pages with room - Both are small files but critical for performance ## TOAST TOAST triggers when a **row** exceeds ~2KB. Large values are compressed and/or moved out-of-line to `pg_toast.pg_toast_` tables. **Strategies:** PLAIN (no TOAST), EXTENDED (compress+out-of-line, default for text/bytea), EXTERNAL (out-of-line, no compression — use for pre-compressed data), MAIN (compress, avoid out-of-line). TOAST tables bloat like regular tables — they need VACUUM. `SELECT *` fetches all TOAST columns; always SELECT only needed columns. Move large rarely-accessed columns to separate tables. ## Fillfactor Controls how full pages are packed (default 100%). Lower fillfactor (70–80%) leaves room for HOT (Heap-Only Tuple) updates, which avoid index entries and reduce bloat on UPDATE-heavy tables. Keep 100% for insert-only or read-mostly tables. `ALTER TABLE t SET (fillfactor = 70);` ## Tablespaces `pg_default` (base/), `pg_global` (global/) are built-in. Custom tablespaces: symbolic links in **pg_tblspc/** to other filesystem locations. Use for separating hot data (SSD) from archives (HDD). Moving tablespaces requires exclusive lock on affected tables. ## Disk Monitoring - `pg_database_size('dbname')`, `pg_total_relation_size('tablename')`, `pg_relation_size('tablename')` - Monitor disk usage: >80% = at risk; >90% = critical (VACUUM may fail if disk capacity is insufficient) - Check inode usage (`df -i`) — can run out even with free space - `pg_wal/` suddenly large = check replication slots and archiving ================================================ FILE: .agents/skills/postgres/references/wal-operations.md ================================================ --- title: WAL and Checkpoint Operations description: Write-ahead log internals, checkpoint tuning, durability guarantees, and WAL disk management tags: postgres, wal, checkpoints, durability, crash-recovery, fsync, operations --- # WAL and Checkpoint Operations ## WAL Fundamentals Write-Ahead Logging: logs changes to `pg_wal/` **before** modifying data files. WAL segments are 16MB (fixed at initdb). On COMMIT, PostgreSQL fsyncs WAL to disk and returns SUCCESS — data files are updated lazily. WAL records are written for all changes (including uncommitted transactions and rollbacks). **Never disable `fsync` in production** — power loss without fsync risks unrecoverable data loss. `wal_level`: `minimal` (crash recovery only), `replica` (default; replication + archiving), `logical` (logical replication). ## Dirty Pages and Checkpoints A dirty page is modified in shared_buffers but not yet written to data files. A checkpoint flushes all dirty pages to disk and writes a checkpoint record to WAL; recovery only replays WAL since the last checkpoint. - `checkpoint_timeout` (default 5 min) and `max_wal_size` (default 1GB) — checkpoint on whichever triggers first. - `checkpoint_completion_target=0.9` spreads I/O over 90% of the interval; avoid spikes. - "Checkpoints are occurring too frequently" in logs → increase `max_wal_size`. - **Target: >90% of checkpoints should be time-based** (`num_timed` in `pg_stat_checkpointer`), not size-based (`num_requested`). If num_requested/(num_timed+num_requested) > 10%, tune `max_wal_size` up. ## WAL Disk Management Replication slots prevent WAL deletion even when standbys are offline — they can fill disk. WAL archiving failures also block recycling. `max_wal_size` is a *soft* limit; WAL can grow beyond it under heavy load. WAL size: `SELECT count(*) AS files, pg_size_pretty(sum(size)) AS total FROM pg_ls_waldir();` Slot lag: `SELECT slot_name, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS lag_bytes FROM pg_replication_slots;` ## Checkpoint Monitoring PG17+ moved checkpoint stats from `pg_stat_bgwriter` to `pg_stat_checkpointer` and renamed columns. `SELECT num_timed, num_requested, write_time, sync_time, buffers_written FROM pg_stat_checkpointer;` Backend-direct writes (formerly `buffers_backend` in `pg_stat_bgwriter`) are now tracked in `pg_stat_io`: `SELECT writes FROM pg_stat_io WHERE backend_type = 'client backend' AND object = 'relation';` ## Crash Recovery On crash, PostgreSQL replays WAL from the last checkpoint. Longer checkpoint intervals → more WAL to replay → longer recovery. Trade-off: frequent checkpoints (faster recovery, more I/O) vs infrequent (less I/O, slower recovery). For most workloads, `checkpoint_timeout=5min` and `max_wal_size` tuned to keep checkpoints time-based is the right balance. ================================================ FILE: .agents/skills/vercel-react-best-practices/AGENTS.md ================================================ # React Best Practices **Version 1.0.0** Vercel Engineering January 2026 > **Note:** > This document is mainly for agents and LLMs to follow when maintaining, > generating, or refactoring React and Next.js codebases. Humans > may also find it useful, but guidance here is optimized for automation > and consistency by AI-assisted workflows. --- ## Abstract Comprehensive performance optimization guide for React and Next.js applications, designed for AI agents and LLMs. Contains 40+ rules across 8 categories, prioritized by impact from critical (eliminating waterfalls, reducing bundle size) to incremental (advanced patterns). Each rule includes detailed explanations, real-world examples comparing incorrect vs. correct implementations, and specific impact metrics to guide automated refactoring and code generation. --- ## Table of Contents 1. [Eliminating Waterfalls](#1-eliminating-waterfalls) — **CRITICAL** - 1.1 [Defer Await Until Needed](#11-defer-await-until-needed) - 1.2 [Dependency-Based Parallelization](#12-dependency-based-parallelization) - 1.3 [Prevent Waterfall Chains in API Routes](#13-prevent-waterfall-chains-in-api-routes) - 1.4 [Promise.all() for Independent Operations](#14-promiseall-for-independent-operations) - 1.5 [Strategic Suspense Boundaries](#15-strategic-suspense-boundaries) 2. [Bundle Size Optimization](#2-bundle-size-optimization) — **CRITICAL** - 2.1 [Avoid Barrel File Imports](#21-avoid-barrel-file-imports) - 2.2 [Conditional Module Loading](#22-conditional-module-loading) - 2.3 [Defer Non-Critical Third-Party Libraries](#23-defer-non-critical-third-party-libraries) - 2.4 [Dynamic Imports for Heavy Components](#24-dynamic-imports-for-heavy-components) - 2.5 [Preload Based on User Intent](#25-preload-based-on-user-intent) 3. [Server-Side Performance](#3-server-side-performance) — **HIGH** - 3.1 [Authenticate Server Actions Like API Routes](#31-authenticate-server-actions-like-api-routes) - 3.2 [Avoid Duplicate Serialization in RSC Props](#32-avoid-duplicate-serialization-in-rsc-props) - 3.3 [Cross-Request LRU Caching](#33-cross-request-lru-caching) - 3.4 [Minimize Serialization at RSC Boundaries](#34-minimize-serialization-at-rsc-boundaries) - 3.5 [Parallel Data Fetching with Component Composition](#35-parallel-data-fetching-with-component-composition) - 3.6 [Per-Request Deduplication with React.cache()](#36-per-request-deduplication-with-reactcache) - 3.7 [Use after() for Non-Blocking Operations](#37-use-after-for-non-blocking-operations) 4. [Client-Side Data Fetching](#4-client-side-data-fetching) — **MEDIUM-HIGH** - 4.1 [Deduplicate Global Event Listeners](#41-deduplicate-global-event-listeners) - 4.2 [Use Passive Event Listeners for Scrolling Performance](#42-use-passive-event-listeners-for-scrolling-performance) - 4.3 [Use SWR for Automatic Deduplication](#43-use-swr-for-automatic-deduplication) - 4.4 [Version and Minimize localStorage Data](#44-version-and-minimize-localstorage-data) 5. [Re-render Optimization](#5-re-render-optimization) — **MEDIUM** - 5.1 [Calculate Derived State During Rendering](#51-calculate-derived-state-during-rendering) - 5.2 [Defer State Reads to Usage Point](#52-defer-state-reads-to-usage-point) - 5.3 [Do not wrap a simple expression with a primitive result type in useMemo](#53-do-not-wrap-a-simple-expression-with-a-primitive-result-type-in-usememo) - 5.4 [Extract Default Non-primitive Parameter Value from Memoized Component to Constant](#54-extract-default-non-primitive-parameter-value-from-memoized-component-to-constant) - 5.5 [Extract to Memoized Components](#55-extract-to-memoized-components) - 5.6 [Narrow Effect Dependencies](#56-narrow-effect-dependencies) - 5.7 [Put Interaction Logic in Event Handlers](#57-put-interaction-logic-in-event-handlers) - 5.8 [Subscribe to Derived State](#58-subscribe-to-derived-state) - 5.9 [Use Functional setState Updates](#59-use-functional-setstate-updates) - 5.10 [Use Lazy State Initialization](#510-use-lazy-state-initialization) - 5.11 [Use Transitions for Non-Urgent Updates](#511-use-transitions-for-non-urgent-updates) - 5.12 [Use useRef for Transient Values](#512-use-useref-for-transient-values) 6. [Rendering Performance](#6-rendering-performance) — **MEDIUM** - 6.1 [Animate SVG Wrapper Instead of SVG Element](#61-animate-svg-wrapper-instead-of-svg-element) - 6.2 [CSS content-visibility for Long Lists](#62-css-content-visibility-for-long-lists) - 6.3 [Hoist Static JSX Elements](#63-hoist-static-jsx-elements) - 6.4 [Optimize SVG Precision](#64-optimize-svg-precision) - 6.5 [Prevent Hydration Mismatch Without Flickering](#65-prevent-hydration-mismatch-without-flickering) - 6.6 [Suppress Expected Hydration Mismatches](#66-suppress-expected-hydration-mismatches) - 6.7 [Use Activity Component for Show/Hide](#67-use-activity-component-for-showhide) - 6.8 [Use Explicit Conditional Rendering](#68-use-explicit-conditional-rendering) - 6.9 [Use useTransition Over Manual Loading States](#69-use-usetransition-over-manual-loading-states) 7. [JavaScript Performance](#7-javascript-performance) — **LOW-MEDIUM** - 7.1 [Avoid Layout Thrashing](#71-avoid-layout-thrashing) - 7.2 [Build Index Maps for Repeated Lookups](#72-build-index-maps-for-repeated-lookups) - 7.3 [Cache Property Access in Loops](#73-cache-property-access-in-loops) - 7.4 [Cache Repeated Function Calls](#74-cache-repeated-function-calls) - 7.5 [Cache Storage API Calls](#75-cache-storage-api-calls) - 7.6 [Combine Multiple Array Iterations](#76-combine-multiple-array-iterations) - 7.7 [Early Length Check for Array Comparisons](#77-early-length-check-for-array-comparisons) - 7.8 [Early Return from Functions](#78-early-return-from-functions) - 7.9 [Hoist RegExp Creation](#79-hoist-regexp-creation) - 7.10 [Use Loop for Min/Max Instead of Sort](#710-use-loop-for-minmax-instead-of-sort) - 7.11 [Use Set/Map for O(1) Lookups](#711-use-setmap-for-o1-lookups) - 7.12 [Use toSorted() Instead of sort() for Immutability](#712-use-tosorted-instead-of-sort-for-immutability) 8. [Advanced Patterns](#8-advanced-patterns) — **LOW** - 8.1 [Initialize App Once, Not Per Mount](#81-initialize-app-once-not-per-mount) - 8.2 [Store Event Handlers in Refs](#82-store-event-handlers-in-refs) - 8.3 [useEffectEvent for Stable Callback Refs](#83-useeffectevent-for-stable-callback-refs) --- ## 1. Eliminating Waterfalls **Impact: CRITICAL** Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains. ### 1.1 Defer Await Until Needed **Impact: HIGH (avoids blocking unused code paths)** Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them. **Incorrect: blocks both branches** ```typescript async function handleRequest(userId: string, skipProcessing: boolean) { const userData = await fetchUserData(userId) if (skipProcessing) { // Returns immediately but still waited for userData return { skipped: true } } // Only this branch uses userData return processUserData(userData) } ``` **Correct: only blocks when needed** ```typescript async function handleRequest(userId: string, skipProcessing: boolean) { if (skipProcessing) { // Returns immediately without waiting return { skipped: true } } // Fetch only when needed const userData = await fetchUserData(userId) return processUserData(userData) } ``` **Another example: early return optimization** ```typescript // Incorrect: always fetches permissions async function updateResource(resourceId: string, userId: string) { const permissions = await fetchPermissions(userId) const resource = await getResource(resourceId) if (!resource) { return { error: 'Not found' } } if (!permissions.canEdit) { return { error: 'Forbidden' } } return await updateResourceData(resource, permissions) } // Correct: fetches only when needed async function updateResource(resourceId: string, userId: string) { const resource = await getResource(resourceId) if (!resource) { return { error: 'Not found' } } const permissions = await fetchPermissions(userId) if (!permissions.canEdit) { return { error: 'Forbidden' } } return await updateResourceData(resource, permissions) } ``` This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive. ### 1.2 Dependency-Based Parallelization **Impact: CRITICAL (2-10× improvement)** For operations with partial dependencies, use `better-all` to maximize parallelism. It automatically starts each task at the earliest possible moment. **Incorrect: profile waits for config unnecessarily** ```typescript const [user, config] = await Promise.all([ fetchUser(), fetchConfig() ]) const profile = await fetchProfile(user.id) ``` **Correct: config and profile run in parallel** ```typescript import { all } from 'better-all' const { user, config, profile } = await all({ async user() { return fetchUser() }, async config() { return fetchConfig() }, async profile() { return fetchProfile((await this.$.user).id) } }) ``` **Alternative without extra dependencies:** ```typescript const userPromise = fetchUser() const profilePromise = userPromise.then(user => fetchProfile(user.id)) const [user, config, profile] = await Promise.all([ userPromise, fetchConfig(), profilePromise ]) ``` We can also create all the promises first, and do `Promise.all()` at the end. Reference: [https://github.com/shuding/better-all](https://github.com/shuding/better-all) ### 1.3 Prevent Waterfall Chains in API Routes **Impact: CRITICAL (2-10× improvement)** In API routes and Server Actions, start independent operations immediately, even if you don't await them yet. **Incorrect: config waits for auth, data waits for both** ```typescript export async function GET(request: Request) { const session = await auth() const config = await fetchConfig() const data = await fetchData(session.user.id) return Response.json({ data, config }) } ``` **Correct: auth and config start immediately** ```typescript export async function GET(request: Request) { const sessionPromise = auth() const configPromise = fetchConfig() const session = await sessionPromise const [config, data] = await Promise.all([ configPromise, fetchData(session.user.id) ]) return Response.json({ data, config }) } ``` For operations with more complex dependency chains, use `better-all` to automatically maximize parallelism (see Dependency-Based Parallelization). ### 1.4 Promise.all() for Independent Operations **Impact: CRITICAL (2-10× improvement)** When async operations have no interdependencies, execute them concurrently using `Promise.all()`. **Incorrect: sequential execution, 3 round trips** ```typescript const user = await fetchUser() const posts = await fetchPosts() const comments = await fetchComments() ``` **Correct: parallel execution, 1 round trip** ```typescript const [user, posts, comments] = await Promise.all([ fetchUser(), fetchPosts(), fetchComments() ]) ``` ### 1.5 Strategic Suspense Boundaries **Impact: HIGH (faster initial paint)** Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads. **Incorrect: wrapper blocked by data fetching** ```tsx async function Page() { const data = await fetchData() // Blocks entire page return (
Sidebar
Header
Footer
) } ``` The entire layout waits for data even though only the middle section needs it. **Correct: wrapper shows immediately, data streams in** ```tsx function Page() { return (
Sidebar
Header
}>
Footer
) } async function DataDisplay() { const data = await fetchData() // Only blocks this component return
{data.content}
} ``` Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data. **Alternative: share promise across components** ```tsx function Page() { // Start fetch immediately, but don't await const dataPromise = fetchData() return (
Sidebar
Header
}>
Footer
) } function DataDisplay({ dataPromise }: { dataPromise: Promise }) { const data = use(dataPromise) // Unwraps the promise return
{data.content}
} function DataSummary({ dataPromise }: { dataPromise: Promise }) { const data = use(dataPromise) // Reuses the same promise return
{data.summary}
} ``` Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together. **When NOT to use this pattern:** - Critical data needed for layout decisions (affects positioning) - SEO-critical content above the fold - Small, fast queries where suspense overhead isn't worth it - When you want to avoid layout shift (loading → content jump) **Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities. --- ## 2. Bundle Size Optimization **Impact: CRITICAL** Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint. ### 2.1 Avoid Barrel File Imports **Impact: CRITICAL (200-800ms import cost, slow builds)** Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`). Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts. **Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph. **Incorrect: imports entire library** ```tsx import { Check, X, Menu } from 'lucide-react' // Loads 1,583 modules, takes ~2.8s extra in dev // Runtime cost: 200-800ms on every cold start import { Button, TextField } from '@mui/material' // Loads 2,225 modules, takes ~4.2s extra in dev ``` **Correct: imports only what you need** ```tsx import Check from 'lucide-react/dist/esm/icons/check' import X from 'lucide-react/dist/esm/icons/x' import Menu from 'lucide-react/dist/esm/icons/menu' // Loads only 3 modules (~2KB vs ~1MB) import Button from '@mui/material/Button' import TextField from '@mui/material/TextField' // Loads only what you use ``` **Alternative: Next.js 13.5+** ```js // next.config.js - use optimizePackageImports module.exports = { experimental: { optimizePackageImports: ['lucide-react', '@mui/material'] } } // Then you can keep the ergonomic barrel imports: import { Check, X, Menu } from 'lucide-react' // Automatically transformed to direct imports at build time ``` Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR. Libraries commonly affected: `lucide-react`, `@mui/material`, `@mui/icons-material`, `@tabler/icons-react`, `react-icons`, `@headlessui/react`, `@radix-ui/react-*`, `lodash`, `ramda`, `date-fns`, `rxjs`, `react-use`. Reference: [https://vercel.com/blog/how-we-optimized-package-imports-in-next-js](https://vercel.com/blog/how-we-optimized-package-imports-in-next-js) ### 2.2 Conditional Module Loading **Impact: HIGH (loads large data only when needed)** Load large data or modules only when a feature is activated. **Example: lazy-load animation frames** ```tsx function AnimationPlayer({ enabled, setEnabled }: { enabled: boolean; setEnabled: React.Dispatch> }) { const [frames, setFrames] = useState(null) useEffect(() => { if (enabled && !frames && typeof window !== 'undefined') { import('./animation-frames.js') .then(mod => setFrames(mod.frames)) .catch(() => setEnabled(false)) } }, [enabled, frames, setEnabled]) if (!frames) return return } ``` The `typeof window !== 'undefined'` check prevents bundling this module for SSR, optimizing server bundle size and build speed. ### 2.3 Defer Non-Critical Third-Party Libraries **Impact: MEDIUM (loads after hydration)** Analytics, logging, and error tracking don't block user interaction. Load them after hydration. **Incorrect: blocks initial bundle** ```tsx import { Analytics } from '@vercel/analytics/react' export default function RootLayout({ children }) { return ( {children} ) } ``` **Correct: loads after hydration** ```tsx import dynamic from 'next/dynamic' const Analytics = dynamic( () => import('@vercel/analytics/react').then(m => m.Analytics), { ssr: false } ) export default function RootLayout({ children }) { return ( {children} ) } ``` ### 2.4 Dynamic Imports for Heavy Components **Impact: CRITICAL (directly affects TTI and LCP)** Use `next/dynamic` to lazy-load large components not needed on initial render. **Incorrect: Monaco bundles with main chunk ~300KB** ```tsx import { MonacoEditor } from './monaco-editor' function CodePanel({ code }: { code: string }) { return } ``` **Correct: Monaco loads on demand** ```tsx import dynamic from 'next/dynamic' const MonacoEditor = dynamic( () => import('./monaco-editor').then(m => m.MonacoEditor), { ssr: false } ) function CodePanel({ code }: { code: string }) { return } ``` ### 2.5 Preload Based on User Intent **Impact: MEDIUM (reduces perceived latency)** Preload heavy bundles before they're needed to reduce perceived latency. **Example: preload on hover/focus** ```tsx function EditorButton({ onClick }: { onClick: () => void }) { const preload = () => { if (typeof window !== 'undefined') { void import('./monaco-editor') } } return ( ) } ``` **Example: preload when feature flag is enabled** ```tsx function FlagsProvider({ children, flags }: Props) { useEffect(() => { if (flags.editorEnabled && typeof window !== 'undefined') { void import('./monaco-editor').then(mod => mod.init()) } }, [flags.editorEnabled]) return {children} } ``` The `typeof window !== 'undefined'` check prevents bundling preloaded modules for SSR, optimizing server bundle size and build speed. --- ## 3. Server-Side Performance **Impact: HIGH** Optimizing server-side rendering and data fetching eliminates server-side waterfalls and reduces response times. ### 3.1 Authenticate Server Actions Like API Routes **Impact: CRITICAL (prevents unauthorized access to server mutations)** Server Actions (functions with `"use server"`) are exposed as public endpoints, just like API routes. Always verify authentication and authorization **inside** each Server Action—do not rely solely on middleware, layout guards, or page-level checks, as Server Actions can be invoked directly. Next.js documentation explicitly states: "Treat Server Actions with the same security considerations as public-facing API endpoints, and verify if the user is allowed to perform a mutation." **Incorrect: no authentication check** ```typescript 'use server' export async function deleteUser(userId: string) { // Anyone can call this! No auth check await db.user.delete({ where: { id: userId } }) return { success: true } } ``` **Correct: authentication inside the action** ```typescript 'use server' import { verifySession } from '@/lib/auth' import { unauthorized } from '@/lib/errors' export async function deleteUser(userId: string) { // Always check auth inside the action const session = await verifySession() if (!session) { throw unauthorized('Must be logged in') } // Check authorization too if (session.user.role !== 'admin' && session.user.id !== userId) { throw unauthorized('Cannot delete other users') } await db.user.delete({ where: { id: userId } }) return { success: true } } ``` **With input validation:** ```typescript 'use server' import { verifySession } from '@/lib/auth' import { z } from 'zod' const updateProfileSchema = z.object({ userId: z.string().uuid(), name: z.string().min(1).max(100), email: z.string().email() }) export async function updateProfile(data: unknown) { // Validate input first const validated = updateProfileSchema.parse(data) // Then authenticate const session = await verifySession() if (!session) { throw new Error('Unauthorized') } // Then authorize if (session.user.id !== validated.userId) { throw new Error('Can only update own profile') } // Finally perform the mutation await db.user.update({ where: { id: validated.userId }, data: { name: validated.name, email: validated.email } }) return { success: true } } ``` Reference: [https://nextjs.org/docs/app/guides/authentication](https://nextjs.org/docs/app/guides/authentication) ### 3.2 Avoid Duplicate Serialization in RSC Props **Impact: LOW (reduces network payload by avoiding duplicate serialization)** RSC→client serialization deduplicates by object reference, not value. Same reference = serialized once; new reference = serialized again. Do transformations (`.toSorted()`, `.filter()`, `.map()`) in client, not server. **Incorrect: duplicates array** ```tsx // RSC: sends 6 strings (2 arrays × 3 items) ``` **Correct: sends 3 strings** ```tsx // RSC: send once // Client: transform there 'use client' const sorted = useMemo(() => [...usernames].sort(), [usernames]) ``` **Nested deduplication behavior:** ```tsx // string[] - duplicates everything usernames={['a','b']} sorted={usernames.toSorted()} // sends 4 strings // object[] - duplicates array structure only users={[{id:1},{id:2}]} sorted={users.toSorted()} // sends 2 arrays + 2 unique objects (not 4) ``` Deduplication works recursively. Impact varies by data type: - `string[]`, `number[]`, `boolean[]`: **HIGH impact** - array + all primitives fully duplicated - `object[]`: **LOW impact** - array duplicated, but nested objects deduplicated by reference **Operations breaking deduplication: create new references** - Arrays: `.toSorted()`, `.filter()`, `.map()`, `.slice()`, `[...arr]` - Objects: `{...obj}`, `Object.assign()`, `structuredClone()`, `JSON.parse(JSON.stringify())` **More examples:** ```tsx // ❌ Bad u.active)} /> // ✅ Good // Do filtering/destructuring in client ``` **Exception:** Pass derived data when transformation is expensive or client doesn't need original. ### 3.3 Cross-Request LRU Caching **Impact: HIGH (caches across requests)** `React.cache()` only works within one request. For data shared across sequential requests (user clicks button A then button B), use an LRU cache. **Implementation:** ```typescript import { LRUCache } from 'lru-cache' const cache = new LRUCache({ max: 1000, ttl: 5 * 60 * 1000 // 5 minutes }) export async function getUser(id: string) { const cached = cache.get(id) if (cached) return cached const user = await db.user.findUnique({ where: { id } }) cache.set(id, user) return user } // Request 1: DB query, result cached // Request 2: cache hit, no DB query ``` Use when sequential user actions hit multiple endpoints needing the same data within seconds. **With Vercel's [Fluid Compute](https://vercel.com/docs/fluid-compute):** LRU caching is especially effective because multiple concurrent requests can share the same function instance and cache. This means the cache persists across requests without needing external storage like Redis. **In traditional serverless:** Each invocation runs in isolation, so consider Redis for cross-process caching. Reference: [https://github.com/isaacs/node-lru-cache](https://github.com/isaacs/node-lru-cache) ### 3.4 Minimize Serialization at RSC Boundaries **Impact: HIGH (reduces data transfer size)** The React Server/Client boundary serializes all object properties into strings and embeds them in the HTML response and subsequent RSC requests. This serialized data directly impacts page weight and load time, so **size matters a lot**. Only pass fields that the client actually uses. **Incorrect: serializes all 50 fields** ```tsx async function Page() { const user = await fetchUser() // 50 fields return } 'use client' function Profile({ user }: { user: User }) { return
{user.name}
// uses 1 field } ``` **Correct: serializes only 1 field** ```tsx async function Page() { const user = await fetchUser() return } 'use client' function Profile({ name }: { name: string }) { return
{name}
} ``` ### 3.5 Parallel Data Fetching with Component Composition **Impact: CRITICAL (eliminates server-side waterfalls)** React Server Components execute sequentially within a tree. Restructure with composition to parallelize data fetching. **Incorrect: Sidebar waits for Page's fetch to complete** ```tsx export default async function Page() { const header = await fetchHeader() return (
{header}
) } async function Sidebar() { const items = await fetchSidebarItems() return } ``` **Correct: both fetch simultaneously** ```tsx async function Header() { const data = await fetchHeader() return
{data}
} async function Sidebar() { const items = await fetchSidebarItems() return } export default function Page() { return (
) } ``` **Alternative with children prop:** ```tsx async function Header() { const data = await fetchHeader() return
{data}
} async function Sidebar() { const items = await fetchSidebarItems() return } function Layout({ children }: { children: ReactNode }) { return (
{children}
) } export default function Page() { return ( ) } ``` ### 3.6 Per-Request Deduplication with React.cache() **Impact: MEDIUM (deduplicates within request)** Use `React.cache()` for server-side request deduplication. Authentication and database queries benefit most. **Usage:** ```typescript import { cache } from 'react' export const getCurrentUser = cache(async () => { const session = await auth() if (!session?.user?.id) return null return await db.user.findUnique({ where: { id: session.user.id } }) }) ``` Within a single request, multiple calls to `getCurrentUser()` execute the query only once. **Avoid inline objects as arguments:** `React.cache()` uses shallow equality (`Object.is`) to determine cache hits. Inline objects create new references each call, preventing cache hits. **Incorrect: always cache miss** ```typescript const getUser = cache(async (params: { uid: number }) => { return await db.user.findUnique({ where: { id: params.uid } }) }) // Each call creates new object, never hits cache getUser({ uid: 1 }) getUser({ uid: 1 }) // Cache miss, runs query again ``` **Correct: cache hit** ```typescript const params = { uid: 1 } getUser(params) // Query runs getUser(params) // Cache hit (same reference) ``` If you must pass objects, pass the same reference: **Next.js-Specific Note:** In Next.js, the `fetch` API is automatically extended with request memoization. Requests with the same URL and options are automatically deduplicated within a single request, so you don't need `React.cache()` for `fetch` calls. However, `React.cache()` is still essential for other async tasks: - Database queries (Prisma, Drizzle, etc.) - Heavy computations - Authentication checks - File system operations - Any non-fetch async work Use `React.cache()` to deduplicate these operations across your component tree. Reference: [https://react.dev/reference/react/cache](https://react.dev/reference/react/cache) ### 3.7 Use after() for Non-Blocking Operations **Impact: MEDIUM (faster response times)** Use Next.js's `after()` to schedule work that should execute after a response is sent. This prevents logging, analytics, and other side effects from blocking the response. **Incorrect: blocks response** ```tsx import { logUserAction } from '@/app/utils' export async function POST(request: Request) { // Perform mutation await updateDatabase(request) // Logging blocks the response const userAgent = request.headers.get('user-agent') || 'unknown' await logUserAction({ userAgent }) return new Response(JSON.stringify({ status: 'success' }), { status: 200, headers: { 'Content-Type': 'application/json' } }) } ``` **Correct: non-blocking** ```tsx import { after } from 'next/server' import { headers, cookies } from 'next/headers' import { logUserAction } from '@/app/utils' export async function POST(request: Request) { // Perform mutation await updateDatabase(request) // Log after response is sent after(async () => { const userAgent = (await headers()).get('user-agent') || 'unknown' const sessionCookie = (await cookies()).get('session-id')?.value || 'anonymous' logUserAction({ sessionCookie, userAgent }) }) return new Response(JSON.stringify({ status: 'success' }), { status: 200, headers: { 'Content-Type': 'application/json' } }) } ``` The response is sent immediately while logging happens in the background. **Common use cases:** - Analytics tracking - Audit logging - Sending notifications - Cache invalidation - Cleanup tasks **Important notes:** - `after()` runs even if the response fails or redirects - Works in Server Actions, Route Handlers, and Server Components Reference: [https://nextjs.org/docs/app/api-reference/functions/after](https://nextjs.org/docs/app/api-reference/functions/after) --- ## 4. Client-Side Data Fetching **Impact: MEDIUM-HIGH** Automatic deduplication and efficient data fetching patterns reduce redundant network requests. ### 4.1 Deduplicate Global Event Listeners **Impact: LOW (single listener for N components)** Use `useSWRSubscription()` to share global event listeners across component instances. **Incorrect: N instances = N listeners** ```tsx function useKeyboardShortcut(key: string, callback: () => void) { useEffect(() => { const handler = (e: KeyboardEvent) => { if (e.metaKey && e.key === key) { callback() } } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }, [key, callback]) } ``` When using the `useKeyboardShortcut` hook multiple times, each instance will register a new listener. **Correct: N instances = 1 listener** ```tsx import useSWRSubscription from 'swr/subscription' // Module-level Map to track callbacks per key const keyCallbacks = new Map void>>() function useKeyboardShortcut(key: string, callback: () => void) { // Register this callback in the Map useEffect(() => { if (!keyCallbacks.has(key)) { keyCallbacks.set(key, new Set()) } keyCallbacks.get(key)!.add(callback) return () => { const set = keyCallbacks.get(key) if (set) { set.delete(callback) if (set.size === 0) { keyCallbacks.delete(key) } } } }, [key, callback]) useSWRSubscription('global-keydown', () => { const handler = (e: KeyboardEvent) => { if (e.metaKey && keyCallbacks.has(e.key)) { keyCallbacks.get(e.key)!.forEach(cb => cb()) } } window.addEventListener('keydown', handler) return () => window.removeEventListener('keydown', handler) }) } function Profile() { // Multiple shortcuts will share the same listener useKeyboardShortcut('p', () => { /* ... */ }) useKeyboardShortcut('k', () => { /* ... */ }) // ... } ``` ### 4.2 Use Passive Event Listeners for Scrolling Performance **Impact: MEDIUM (eliminates scroll delay caused by event listeners)** Add `{ passive: true }` to touch and wheel event listeners to enable immediate scrolling. Browsers normally wait for listeners to finish to check if `preventDefault()` is called, causing scroll delay. **Incorrect:** ```typescript useEffect(() => { const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX) const handleWheel = (e: WheelEvent) => console.log(e.deltaY) document.addEventListener('touchstart', handleTouch) document.addEventListener('wheel', handleWheel) return () => { document.removeEventListener('touchstart', handleTouch) document.removeEventListener('wheel', handleWheel) } }, []) ``` **Correct:** ```typescript useEffect(() => { const handleTouch = (e: TouchEvent) => console.log(e.touches[0].clientX) const handleWheel = (e: WheelEvent) => console.log(e.deltaY) document.addEventListener('touchstart', handleTouch, { passive: true }) document.addEventListener('wheel', handleWheel, { passive: true }) return () => { document.removeEventListener('touchstart', handleTouch) document.removeEventListener('wheel', handleWheel) } }, []) ``` **Use passive when:** tracking/analytics, logging, any listener that doesn't call `preventDefault()`. **Don't use passive when:** implementing custom swipe gestures, custom zoom controls, or any listener that needs `preventDefault()`. ### 4.3 Use SWR for Automatic Deduplication **Impact: MEDIUM-HIGH (automatic deduplication)** SWR enables request deduplication, caching, and revalidation across component instances. **Incorrect: no deduplication, each instance fetches** ```tsx function UserList() { const [users, setUsers] = useState([]) useEffect(() => { fetch('/api/users') .then(r => r.json()) .then(setUsers) }, []) } ``` **Correct: multiple instances share one request** ```tsx import useSWR from 'swr' function UserList() { const { data: users } = useSWR('/api/users', fetcher) } ``` **For immutable data:** ```tsx import { useImmutableSWR } from '@/lib/swr' function StaticContent() { const { data } = useImmutableSWR('/api/config', fetcher) } ``` **For mutations:** ```tsx import { useSWRMutation } from 'swr/mutation' function UpdateButton() { const { trigger } = useSWRMutation('/api/user', updateUser) return } ``` Reference: [https://swr.vercel.app](https://swr.vercel.app) ### 4.4 Version and Minimize localStorage Data **Impact: MEDIUM (prevents schema conflicts, reduces storage size)** Add version prefix to keys and store only needed fields. Prevents schema conflicts and accidental storage of sensitive data. **Incorrect:** ```typescript // No version, stores everything, no error handling localStorage.setItem('userConfig', JSON.stringify(fullUserObject)) const data = localStorage.getItem('userConfig') ``` **Correct:** ```typescript const VERSION = 'v2' function saveConfig(config: { theme: string; language: string }) { try { localStorage.setItem(`userConfig:${VERSION}`, JSON.stringify(config)) } catch { // Throws in incognito/private browsing, quota exceeded, or disabled } } function loadConfig() { try { const data = localStorage.getItem(`userConfig:${VERSION}`) return data ? JSON.parse(data) : null } catch { return null } } // Migration from v1 to v2 function migrate() { try { const v1 = localStorage.getItem('userConfig:v1') if (v1) { const old = JSON.parse(v1) saveConfig({ theme: old.darkMode ? 'dark' : 'light', language: old.lang }) localStorage.removeItem('userConfig:v1') } } catch {} } ``` **Store minimal fields from server responses:** ```typescript // User object has 20+ fields, only store what UI needs function cachePrefs(user: FullUser) { try { localStorage.setItem('prefs:v1', JSON.stringify({ theme: user.preferences.theme, notifications: user.preferences.notifications })) } catch {} } ``` **Always wrap in try-catch:** `getItem()` and `setItem()` throw in incognito/private browsing (Safari, Firefox), when quota exceeded, or when disabled. **Benefits:** Schema evolution via versioning, reduced storage size, prevents storing tokens/PII/internal flags. --- ## 5. Re-render Optimization **Impact: MEDIUM** Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness. ### 5.1 Calculate Derived State During Rendering **Impact: MEDIUM (avoids redundant renders and state drift)** If a value can be computed from current props/state, do not store it in state or update it in an effect. Derive it during render to avoid extra renders and state drift. Do not set state in effects solely in response to prop changes; prefer derived values or keyed resets instead. **Incorrect: redundant state and effect** ```tsx function Form() { const [firstName, setFirstName] = useState('First') const [lastName, setLastName] = useState('Last') const [fullName, setFullName] = useState('') useEffect(() => { setFullName(firstName + ' ' + lastName) }, [firstName, lastName]) return

{fullName}

} ``` **Correct: derive during render** ```tsx function Form() { const [firstName, setFirstName] = useState('First') const [lastName, setLastName] = useState('Last') const fullName = firstName + ' ' + lastName return

{fullName}

} ``` Reference: [https://react.dev/learn/you-might-not-need-an-effect](https://react.dev/learn/you-might-not-need-an-effect) ### 5.2 Defer State Reads to Usage Point **Impact: MEDIUM (avoids unnecessary subscriptions)** Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks. **Incorrect: subscribes to all searchParams changes** ```tsx function ShareButton({ chatId }: { chatId: string }) { const searchParams = useSearchParams() const handleShare = () => { const ref = searchParams.get('ref') shareChat(chatId, { ref }) } return } ``` **Correct: reads on demand, no subscription** ```tsx function ShareButton({ chatId }: { chatId: string }) { const handleShare = () => { const params = new URLSearchParams(window.location.search) const ref = params.get('ref') shareChat(chatId, { ref }) } return } ``` ### 5.3 Do not wrap a simple expression with a primitive result type in useMemo **Impact: LOW-MEDIUM (wasted computation on every render)** When an expression is simple (few logical or arithmetical operators) and has a primitive result type (boolean, number, string), do not wrap it in `useMemo`. Calling `useMemo` and comparing hook dependencies may consume more resources than the expression itself. **Incorrect:** ```tsx function Header({ user, notifications }: Props) { const isLoading = useMemo(() => { return user.isLoading || notifications.isLoading }, [user.isLoading, notifications.isLoading]) if (isLoading) return // return some markup } ``` **Correct:** ```tsx function Header({ user, notifications }: Props) { const isLoading = user.isLoading || notifications.isLoading if (isLoading) return // return some markup } ``` ### 5.4 Extract Default Non-primitive Parameter Value from Memoized Component to Constant **Impact: MEDIUM (restores memoization by using a constant for default value)** When memoized component has a default value for some non-primitive optional parameter, such as an array, function, or object, calling the component without that parameter results in broken memoization. This is because new value instances are created on every rerender, and they do not pass strict equality comparison in `memo()`. To address this issue, extract the default value into a constant. **Incorrect: `onClick` has different values on every rerender** ```tsx const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) { // ... }) // Used without optional onClick ``` **Correct: stable default value** ```tsx const NOOP = () => {}; const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) { // ... }) // Used without optional onClick ``` ### 5.5 Extract to Memoized Components **Impact: MEDIUM (enables early returns)** Extract expensive work into memoized components to enable early returns before computation. **Incorrect: computes avatar even when loading** ```tsx function Profile({ user, loading }: Props) { const avatar = useMemo(() => { const id = computeAvatarId(user) return }, [user]) if (loading) return return
{avatar}
} ``` **Correct: skips computation when loading** ```tsx const UserAvatar = memo(function UserAvatar({ user }: { user: User }) { const id = useMemo(() => computeAvatarId(user), [user]) return }) function Profile({ user, loading }: Props) { if (loading) return return (
) } ``` **Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders. ### 5.6 Narrow Effect Dependencies **Impact: LOW (minimizes effect re-runs)** Specify primitive dependencies instead of objects to minimize effect re-runs. **Incorrect: re-runs on any user field change** ```tsx useEffect(() => { console.log(user.id) }, [user]) ``` **Correct: re-runs only when id changes** ```tsx useEffect(() => { console.log(user.id) }, [user.id]) ``` **For derived state, compute outside effect:** ```tsx // Incorrect: runs on width=767, 766, 765... useEffect(() => { if (width < 768) { enableMobileMode() } }, [width]) // Correct: runs only on boolean transition const isMobile = width < 768 useEffect(() => { if (isMobile) { enableMobileMode() } }, [isMobile]) ``` ### 5.7 Put Interaction Logic in Event Handlers **Impact: MEDIUM (avoids effect re-runs and duplicate side effects)** If a side effect is triggered by a specific user action (submit, click, drag), run it in that event handler. Do not model the action as state + effect; it makes effects re-run on unrelated changes and can duplicate the action. **Incorrect: event modeled as state + effect** ```tsx function Form() { const [submitted, setSubmitted] = useState(false) const theme = useContext(ThemeContext) useEffect(() => { if (submitted) { post('/api/register') showToast('Registered', theme) } }, [submitted, theme]) return } ``` **Correct: do it in the handler** ```tsx function Form() { const theme = useContext(ThemeContext) function handleSubmit() { post('/api/register') showToast('Registered', theme) } return } ``` Reference: [https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler](https://react.dev/learn/removing-effect-dependencies#should-this-code-move-to-an-event-handler) ### 5.8 Subscribe to Derived State **Impact: MEDIUM (reduces re-render frequency)** Subscribe to derived boolean state instead of continuous values to reduce re-render frequency. **Incorrect: re-renders on every pixel change** ```tsx function Sidebar() { const width = useWindowWidth() // updates continuously const isMobile = width < 768 return