Files
supabase/apps/studio/components
Douglas J Hunley eea39cc316 fix(studio): register pitr_archiving_stale in the advisor lintInfoMap (#48044)
## Summary
Studio's Advisor UI reads lint metadata from a fixed `lintInfoMap`, not
from the API response. A lint name missing from that map shows a blank
title, no icon, no filter checkbox, and no remediation link. This PR
adds a `pitr_archiving_stale` entry to `lintInfoMap`, copied from the
existing `pitr_not_enabled` entry, so the new lint renders correctly in
the Advisor UI.

## Dependencies

> [!WARNING]
>
[supabase/platform#35862](https://github.com/supabase/platform/pull/35862)
defines the `pitr_archiving_stale` lint. Until it merges, the API never
sends this lint name, so the Advisor grid and the public
`/v1/projects/{ref}/advisors/security` response never show the new row
-- but the Security Rules page
(`/project/<ref>/advisors/rules/security`) renders one row per
`lintInfoMap` entry regardless of the API, so this PR's new row appears
there immediately, before the backend lint exists. See Details for what
that means in the gap between merges.

---

<details>
<summary>Details</summary>

- A lint name missing from `lintInfoMap` has these effects:
- The grid row shows a blank title and no icon. There is no fallback to
the API's own `title`.
- The row has no filter checkbox. Filter options come from
`lintInfoMap`, not from the API.
- The row has no lint-specific remediation link. The "Learn more" link
falls back to the generic database-linter page.
  - The row does not appear in the Advisor Rules enable/disable list.
- The new `pitr_archiving_stale` entry copies the existing
`pitr_not_enabled` entry's `link`, `docsLink`, and `category`, and uses
a new `title` matching
[supabase/platform#35862](https://github.com/supabase/platform/pull/35862)'s
lint definition verbatim. Its `name` also matches that lint definition
exactly.
- **Known gap, until the backend PR merges:** `AdvisorRules`
(`components/interfaces/Advisors/AdvisorRules.tsx`) filters
`lintInfoMap` by `category` alone, with no dependency on the API
returning the lint -- so this entry makes a "PITR archiving may be
broken" row appear on the Security Rules page for every project right
away, ahead of the backend lint actually existing. From that row, a user
can open `CreateRuleSheet` and submit a disable rule, which `POST`s
`lint_name: 'pitr_archiving_stale'` to the notification-exceptions
endpoint. That name is not yet in the generated
`CreateNotificationExceptionsBody` enum
(`packages/api-types/types/platform.d.ts`), so the request either errors
or stores an exception keyed to a lint name nothing will ever match,
until api-types regenerates after the backend PR ships. This window
closes on its own once
[supabase/platform#35862](https://github.com/supabase/platform/pull/35862)
merges; accepted as a short-lived tradeoff rather than gating this PR on
merge order or adding code to hide the row until then.
- The docs anchor (`#point-in-time-recovery`) explains what PITR and
WAL-G archiving are. It does not explain how to fix a stale or broken
archive. That content does not exist yet in either pull request.
INDATA-1149 tracks this as a follow-up.
- `packages/api-types/types/platform.d.ts` is a generated file. This
repo's own CLAUDE.md says never to hand-edit it. The file does not list
`pitr_archiving_stale` yet, because it regenerates only after the
backend lint ships and `pnpm api:codegen` runs. Until then,
`LintInfo['name']` stays a plain `string`. If someone misspells the new
entry's `name`, the code still compiles and the tests still pass. At
runtime, the icon and docs link fall back silently instead of failing a
build. Once
[supabase/platform#35862](https://github.com/supabase/platform/pull/35862)
merges and api-types regenerates, `LintInfo['name']` must tighten to the
generated `LINT_TYPES` union. This closes the gap for every lint entry,
not only this one.

</details>

---

<details>
<summary>Testing</summary>

- `pnpm --filter=studio test Linter.utils.test.tsx` (17 passed,
including a test that asserts the `pitr_archiving_stale` entry's shape)
- `pnpm typecheck --filter=studio` (clean)
- `pnpm exec eslint` on the touched files (clean; the `pnpm lint
--filter=studio` turbo wrapper itself errors on this machine with an
unrelated JSON-parse failure -- a tool-invocation issue, not a lint
finding)
- `prettier --check` on the touched files
- The `docsLink` assertion
(`toContain('/guides/platform/backups#point-in-time-recovery')`) is
domain-agnostic by construction, so it holds regardless of which
`NEXT_PUBLIC_DOCS_URL` value is set -- no test in this file overrides
that variable, this is a property of the assertion's own shape, not a
scenario the suite exercises

</details>

---

<details>
<summary>Misc</summary>

- Part of INDATA-979
- Changelog:
[supabase/changelog#192](https://github.com/supabase/changelog/pull/192)

</details>
2026-08-31 15:28:37 -04:00
..

Writing components

Where to create your components

  • For components that declare the general structure and layout of a page:
    • /components/layouts/xxx
  • For components that are tightly coupled to a specific interface:
    • /components/interfaces/xxx
  • For components that are meant to be reusable across multiple pages:
    • /components/ui/xxx
  • Note: We're gradually moving files out of the to-be-cleaned folder into the respective folders as we refactor

Component structure

  • If a component has constants and utility methods that are tightly coupled to itself, keep them close to the component and enclose them in a folder with an index.tsx as an entry point
  • Otherwise it can just be a file on its own
  • For example:
    • components/ui
      - SampleComponentA
        - SampleComponentA.tsx
        - SampleComponentA.constants.ts
        - SampleComponentA.utils.ts
        - SampleComponentA.types.ts
        - index.ts
      - SampleComponentB.tsx
      

Template for building components


// Declare the prop types of your component
interface ComponentAProps {
  sampleProp: string
}

// Name your component accordingly — use a named export, not a default export
export const ComponentA = ({ sampleProp }: ComponentAProps) => {
  return <div>ComponentA: {sampleProp}</div>
}