Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions .coderabbit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,54 @@ reviews:
for both runtimes until the final cleanup pass (tracked in FE-3106).
Keep this a reminder to verify, not a hard blocker: if no mirror is required,
say so briefly rather than forcing a change.
- path: '{apps,packages}/**/*.{tsx,jsx,css,mdx}'
instructions: |
When reviewing UI changes, flag these accessibility gaps. Comments are
advisory. One comment per gap. Skip test files (*.test.*, *.spec.*) and
generated files. Skip Radix/shadcn primitives imported from ui for all
checks below. Do not flag issues axe-core already catches mechanically,
such as a missing alt attribute, an empty button or link name, or
invalid ARIA.
- State changes: if sighted users can see a status change (toast,
loading/empty swap, copy confirmation, async result) and nothing
announces it, suggest aria-live="polite" or role="status". Reserve
role="alert" for urgent errors or warnings. Skip if a live region,
Radix Toast, or Sonner is already there, or if the change is
decoration only. If a live region is created in the same conditional
as its message, flag that: the region must already exist in the DOM,
then receive the update, or screen readers often announce nothing.
- Mouse interaction: flag pointer-only handlers on a non-interactive
element (div, span, or similar) with no keyboard equivalent. The
listed handlers are illustrative: onClick, onMouseEnter,
onDoubleClick, onContextMenu, onPointerDown, onPointerUp,
onTouchStart, onTouchEnd, and equivalents. Also flag hover-only UI
(content revealed with onMouseEnter or CSS :hover) that has no focus
or keyboard path.
- Animation: flag animate-*, keyframes, or JS motion with no
reduced-motion treatment. Prefer Tailwind motion-reduce: /
motion-safe:, or matchMedia('(prefers-reduced-motion: reduce)').
packages/config/css/utilities.css only zeroes out .shimmer under
reduced motion, not all animation.
- Alt text: flag generic values such as Image, Icon, Photo, Picture, or
the filename. Flag alt that starts with "image of" or "picture of".
If adjacent visible text already names the image (blog thumbnail next
to its title, icon next to its label), flag it as redundant and
recommend alt="" plus aria-hidden on the image. For a decorative SVG
next to visible text, recommend aria-hidden on the SVG. If alt is
longer than about two sentences, suggest moving the extra into a
caption, adjacent text, or aria-describedby. Do not treat a character
count as a hard fail.
- Focus visibility: flag outline-none, outline-hidden, outline: none,
outline: 0, or equivalent :focus resets that are not paired with a
focus-visible ring or outline, or with the focus-ring or focus-inset
utility.
- Color-only state: flag status, validation, or selection that is
conveyed only by color. Suggest a text label, icon, or sr-only text
in addition.
- Link purpose: flag an accessible name that is only "click here",
"read more", or "learn more" when it does not describe the
destination. Skip if aria-label or wrapping context already names
where the link goes.

# Applies our internal engineering skills (.claude/skills/) as CodeRabbit review
# guidelines. The skills are the single source of truth — they are consumed
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/data/content-listings/ai-tools.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ const ICON_ASSETS: Record<string, { icon: string; hasLightIcon?: boolean }> = {
'claude-code': { icon: '/docs/img/icons/agent-claude-icon', hasLightIcon: false },
codex: { icon: '/docs/img/icons/agent-openai-icon', hasLightIcon: true },
cursor: { icon: '/docs/img/icons/agent-cursor-icon', hasLightIcon: true },
grok: { icon: '/docs/img/icons/agent-grok-icon', hasLightIcon: true },
'gemini-cli': { icon: '/docs/img/icons/agent-gemini-cli-icon', hasLightIcon: false },
'github-copilot': { icon: '/docs/img/icons/agent-copilot-icon', hasLightIcon: true },
kimi: { icon: '/docs/img/icons/agent-kimi-icon', hasLightIcon: true },
vscode: { icon: '/docs/img/icons/agent-vscode-icon', hasLightIcon: false },
antigravity: { icon: '/docs/img/icons/agent-antigravity-icon', hasLightIcon: false },
windsurf: { icon: '/docs/img/icons/agent-devin-icon', hasLightIcon: true },
warp: { icon: '/docs/img/icons/agent-warp-icon', hasLightIcon: true },
goose: { icon: '/docs/img/icons/agent-goose-icon', hasLightIcon: true },
factory: { icon: '/docs/img/icons/agent-factory-icon', hasLightIcon: true },
opencode: { icon: '/docs/img/icons/agent-opencode-icon', hasLightIcon: true },
Expand Down
11 changes: 11 additions & 0 deletions apps/docs/features/ui/AgentPluginsPanel.data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { McpClient } from 'ui-patterns/McpUrlBuilder'
export interface PluginClient extends McpClient {
repoUrl?: string
docsUrl?: string
docsLinkText?: string
}

export const PLUGIN_CLIENTS: PluginClient[] = [
Expand All @@ -25,6 +26,7 @@ export const PLUGIN_CLIENTS: PluginClient[] = [
key: 'cursor',
label: 'Cursor',
icon: 'cursor',
hasDistinctDarkIcon: true,
repoUrl: 'https://github.com/supabase-community/cursor-plugin',
docsUrl: 'https://cursor.com/docs/plugins',
},
Expand All @@ -44,6 +46,15 @@ export const PLUGIN_CLIENTS: PluginClient[] = [
docsUrl:
'https://docs.github.com/en/copilot/how-tos/copilot-cli/customize-copilot/plugins-finding-installing',
},
{
key: 'grok',
label: 'Grok',
icon: 'grok',
hasDistinctDarkIcon: true,
repoUrl: 'https://github.com/supabase-community/supabase-plugin',
docsUrl: 'https://docs.x.ai/build/features/skills-plugins-marketplaces#plugins',
docsLinkText: 'View Grok plugins docs',
},
{
key: 'kimi',
label: 'Kimi Code',
Expand Down
22 changes: 21 additions & 1 deletion apps/docs/features/ui/AgentPluginsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,26 @@ function PluginInstructions({ client }: { client: PluginClient }) {
)
}

if (client.key === 'grok') {
return (
<div className="space-y-3">
<p className="text-sm text-foreground-light">
Install the Supabase plugin by running the following command in your terminal.
</p>
<CodeBlock
value="grok plugin install supabase-community/supabase-plugin"
language="bash"
focusable={false}
className="block"
/>
<p className="text-xs text-foreground-lighter">
Browse and install plugins in a session: run <code>grok</code>, then <code>/plugins</code>{' '}
or <code>/marketplace</code>.
</p>
</div>
)
}

if (client.key === 'vscode') {
return (
<div className="space-y-3">
Expand Down Expand Up @@ -240,7 +260,7 @@ export function AgentPluginsPanel() {
rel="noopener noreferrer"
className="text-brand-link hover:underline inline-flex items-center"
>
View {selectedClient.label} extensions docs
{selectedClient.docsLinkText ?? `View ${selectedClient.label} extensions docs`}
<ExternalLink className="h-3 w-3 ml-1" />
</a>
</div>
Expand Down
14 changes: 3 additions & 11 deletions apps/docs/features/ui/McpConfigPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
import { Admonition } from 'ui-patterns/Admonition'
import {
createMcpCopyHandler,
MCP_HOSTED_AUTH_NOTE,
McpConfigPanel as McpConfigPanelBase,
type McpClient,
} from 'ui-patterns/McpUrlBuilder'
Expand Down Expand Up @@ -336,17 +337,8 @@ export function McpConfigPanel() {
/>
</div>
{isPlatform && (
<Admonition type="note" title="Authentication" className="mt-3">
<p>
{
"Some MCP clients will automatically prompt you to login during setup, while others may require manual authentication steps. Either authentication method will open a browser window where you can login to your Supabase account and grant organization access to the MCP client. In the future, we'll offer more fine grain control over these permissions."
}
</p>
<p>
{
'Previously Supabase MCP required you to generate a personal access token (PAT), but this is no longer required.'
}
</p>
<Admonition type="note" title={MCP_HOSTED_AUTH_NOTE.title} className="mt-3">
<p>{MCP_HOSTED_AUTH_NOTE.body}</p>
</Admonition>
)}
</>
Expand Down
1 change: 1 addition & 0 deletions apps/docs/public/humans.txt
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ Dimitrios Liappis
Div Arora
Divit D
Divya Sharma
Donna Alexandra
Douglas Hunley
Eduardo Gurgel
Eleftheria Trivyzaki
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/public/img/icons/agent-grok-icon-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions apps/docs/public/img/icons/agent-grok-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/docs/public/img/icons/agent-warp-icon-light.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions apps/docs/public/img/icons/agent-warp-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { NotebookPen } from 'lucide-react'
import { useState } from 'react'
import { Button } from 'ui'
import { Button, cn } from 'ui'

import {
formatNotebookDiffSummary,
getEntryKey,
isEntryExpandedByDefault,
summarizeNotebookDiff,
} from './NotebookPreview.utils'
import { NotebookPreviewCell } from './NotebookPreviewCell'
Expand All @@ -12,34 +14,92 @@ import type { NotebookCellDiffEntry } from '@/data/content/notebooks/notebook-op
export interface NotebookPreviewProps {
entries: NotebookCellDiffEntry[]
mode: 'create' | 'update'
/** The notebook's name, shown in the card header. Falls back to a generic label. */
title?: string
className?: string
}

const VISIBLE_ENTRY_LIMIT = 5

const FALLBACK_TITLE = {
create: 'New notebook',
update: 'Notebook changes',
} as const

/**
* Read-only preview of a proposed notebook create/update, rendered from a pre-computed diff.
* Pure presentational component: no data fetching, no approval or notebook-editor state — see
* `deriveNotebookDiff` for how `entries` is produced.
*/
export const NotebookPreview = ({ entries, mode }: NotebookPreviewProps) => {
const [isExpanded, setIsExpanded] = useState(false)
export const NotebookPreview = ({ entries, mode, title, className }: NotebookPreviewProps) => {
const [isShowingAllEntries, setIsShowingAllEntries] = useState(false)
const [expandedOverrides, setExpandedOverrides] = useState<Record<string, boolean>>({})

const summary = summarizeNotebookDiff(entries, mode)
const visibleEntries = isExpanded ? entries : entries.slice(0, VISIBLE_ENTRY_LIMIT)
const visibleEntries = isShowingAllEntries ? entries : entries.slice(0, VISIBLE_ENTRY_LIMIT)
const hiddenCount = entries.length - visibleEntries.length

const isExpanded = (entry: NotebookCellDiffEntry) =>
expandedOverrides[getEntryKey(entry)] ?? isEntryExpandedByDefault(entry)

const hasEntries = entries.length > 0
const areAllExpanded = visibleEntries.every(isExpanded)

const toggleAll = () =>
setExpandedOverrides(
Object.fromEntries(entries.map((entry) => [getEntryKey(entry), !areAllExpanded]))
)

return (
<div className="flex flex-col gap-2">
<p className="text-xs text-foreground-light font-mono">
{formatNotebookDiffSummary(summary)}
</p>
<div className="flex flex-col gap-1.5">
{visibleEntries.map((entry) => (
<NotebookPreviewCell key={getEntryKey(entry)} entry={entry} />
))}
<div
className={cn('overflow-hidden rounded-lg border border-default bg-surface-100', className)}
>
<div className="flex items-center gap-2 border-b border-default bg-surface-200 px-3 py-1.5">
<NotebookPen
aria-hidden={true}
size={13}
strokeWidth={1.5}
className="shrink-0 text-foreground-lighter"
/>
<span className="min-w-0 flex-1 truncate text-xs font-medium text-foreground">
{title ?? FALLBACK_TITLE[mode]}
</span>
<span className="heading-meta shrink-0 text-foreground-light">
{formatNotebookDiffSummary(summary)}
</span>
{hasEntries && (
<Button
variant="text"
size="tiny"
className="-mr-1.5 shrink-0 px-1.5 text-foreground-lighter hover:text-foreground"
onClick={toggleAll}
>
{areAllExpanded ? 'Collapse all' : 'Expand all'}
</Button>
)}
</div>
<div className="divide-y divide-border">
{visibleEntries.map((entry) => {
const key = getEntryKey(entry)
return (
<NotebookPreviewCell
key={key}
entry={entry}
isExpanded={isExpanded(entry)}
onExpandedChange={(open) =>
setExpandedOverrides((prev) => ({ ...prev, [key]: open }))
}
/>
)
})}
</div>
{hiddenCount > 0 && (
<Button variant="text" className="w-full" onClick={() => setIsExpanded(true)}>
<Button
variant="text"
size="tiny"
className="w-full rounded-none border-0 border-t border-default text-foreground-light"
onClick={() => setIsShowingAllEntries(true)}
>
Show {hiddenCount} more cell{hiddenCount === 1 ? '' : 's'}
</Button>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ export function getEntryKey(entry: NotebookCellDiffEntry): string {
}
}

/**
* Whether a diff entry starts expanded. Only the entries the user has to actually read to
* decide — the ones whose content the assistant is proposing — open on their own; unchanged,
* moved, and removed cells stay as single rows until asked for.
*/
export function isEntryExpandedByDefault(entry: NotebookCellDiffEntry): boolean {
return entry._tag === 'added' || entry._tag === 'replaced'
}

/** Human label for a collapsed/badge row. */
export function getCellLabel(cell: CellWire | AgentCell): string {
switch (cell._tag) {
Expand Down
Loading
Loading