Issues
64 of 64No filters
64 results
| DT-400 | Checkout fails for saved cards | Backlog | urgent | Ada | 1 | 2026-09-30 |
| DT-401 | Search results miss archived projects | In review | low | Noor | 13 | 2026-08-24 |
| DT-402 | Invite email lands in spam | Todo | medium | Lin | 8 | 2026-08-27 |
| DT-403 | Dark mode contrast on charts | Done | high | Kai | 5 | 2026-08-30 |
| DT-404 | Rate limit headers for public API | In progress | urgent | Sam | 3 | 2026-09-02 |
| DT-405 | SSO loop on Safari | Backlog | low | Ada | 2 | 2026-09-05 |
| DT-406 | Export to CSV drops unicode | In review | medium | Noor | 1 | 2026-09-08 |
| DT-407 | Slow dashboard on large workspaces | Todo | high | Lin | 13 | 2026-09-11 |
| DT-408 | Billing page shows wrong currency | Done | urgent | Kai | 8 | 2026-09-14 |
| DT-409 | Keyboard trap in date picker | In progress | low | Sam | 5 | 2026-09-17 |
| DT-410 | Webhook retries flood the queue | Backlog | medium | Ada | 3 | 2026-09-20 |
| DT-411 | Onboarding checklist resets | In review | high | Noor | 2 | 2026-09-23 |
| DT-412 | Checkout fails for saved cards (2) | Todo | urgent | Lin | 1 | 2026-09-26 |
| DT-413 | Search results miss archived projects (2) | Done | low | Kai | 13 | 2026-09-29 |
| DT-414 | Invite email lands in spam (2) | In progress | medium | Sam | 8 | 2026-08-23 |
| DT-415 | Dark mode contrast on charts (2) | Backlog | high | Ada | 5 | 2026-08-26 |
| DT-416 | Rate limit headers for public API (2) | In review | urgent | Noor | 3 | 2026-08-29 |
| DT-417 | SSO loop on Safari (2) | Todo | low | Lin | 2 | 2026-09-01 |
| DT-418 | Export to CSV drops unicode (2) | Done | medium | Kai | 1 | 2026-09-04 |
| DT-419 | Slow dashboard on large workspaces (2) | In progress | high | Sam | 13 | 2026-09-07 |
| DT-420 | Billing page shows wrong currency (2) | Backlog | urgent | Ada | 8 | 2026-09-10 |
| DT-421 | Keyboard trap in date picker (2) | In review | low | Noor | 5 | 2026-09-13 |
| DT-422 | Webhook retries flood the queue (2) | Todo | medium | Lin | 3 | 2026-09-16 |
| DT-423 | Onboarding checklist resets (2) | Done | high | Kai | 2 | 2026-09-19 |
| DT-424 | Checkout fails for saved cards (3) | In progress | urgent | Sam | 1 | 2026-09-22 |
| DT-425 | Search results miss archived projects (3) | Backlog | low | Ada | 13 | 2026-09-25 |
| DT-426 | Invite email lands in spam (3) | In review | medium | Noor | 8 | 2026-09-28 |
| DT-427 | Dark mode contrast on charts (3) | Todo | high | Lin | 5 | 2026-08-22 |
| DT-428 | Rate limit headers for public API (3) | Done | urgent | Kai | 3 | 2026-08-25 |
| DT-429 | SSO loop on Safari (3) | In progress | low | Sam | 2 | 2026-08-28 |
| DT-430 | Export to CSV drops unicode (3) | Backlog | medium | Ada | 1 | 2026-08-31 |
| DT-431 | Slow dashboard on large workspaces (3) | In review | high | Noor | 13 | 2026-09-03 |
| DT-432 | Billing page shows wrong currency (3) | Todo | urgent | Lin | 8 | 2026-09-06 |
| DT-433 | Keyboard trap in date picker (3) | Done | low | Kai | 5 | 2026-09-09 |
| DT-434 | Webhook retries flood the queue (3) | In progress | medium | Sam | 3 | 2026-09-12 |
| DT-435 | Onboarding checklist resets (3) | Backlog | high | Ada | 2 | 2026-09-15 |
| DT-436 | Checkout fails for saved cards (4) | In review | urgent | Noor | 1 | 2026-09-18 |
| DT-437 | Search results miss archived projects (4) | Todo | low | Lin | 13 | 2026-09-21 |
| DT-438 | Invite email lands in spam (4) | Done | medium | Kai | 8 | 2026-09-24 |
| DT-439 | Dark mode contrast on charts (4) | In progress | high | Sam | 5 | 2026-09-27 |
| DT-440 | Rate limit headers for public API (4) | Backlog | urgent | Ada | 3 | 2026-09-30 |
| DT-441 | SSO loop on Safari (4) | In review | low | Noor | 2 | 2026-08-24 |
| DT-442 | Export to CSV drops unicode (4) | Todo | medium | Lin | 1 | 2026-08-27 |
| DT-443 | Slow dashboard on large workspaces (4) | Done | high | Kai | 13 | 2026-08-30 |
| DT-444 | Billing page shows wrong currency (4) | In progress | urgent | Sam | 8 | 2026-09-02 |
| DT-445 | Keyboard trap in date picker (4) | Backlog | low | Ada | 5 | 2026-09-05 |
| DT-446 | Webhook retries flood the queue (4) | In review | medium | Noor | 3 | 2026-09-08 |
| DT-447 | Onboarding checklist resets (4) | Todo | high | Lin | 2 | 2026-09-11 |
| DT-448 | Checkout fails for saved cards (5) | Done | urgent | Kai | 1 | 2026-09-14 |
| DT-449 | Search results miss archived projects (5) | In progress | low | Sam | 13 | 2026-09-17 |
| DT-450 | Invite email lands in spam (5) | Backlog | medium | Ada | 8 | 2026-09-20 |
| DT-451 | Dark mode contrast on charts (5) | In review | high | Noor | 5 | 2026-09-23 |
| DT-452 | Rate limit headers for public API (5) | Todo | urgent | Lin | 3 | 2026-09-26 |
| DT-453 | SSO loop on Safari (5) | Done | low | Kai | 2 | 2026-09-29 |
| DT-454 | Export to CSV drops unicode (5) | In progress | medium | Sam | 1 | 2026-08-23 |
| DT-455 | Slow dashboard on large workspaces (5) | Backlog | high | Ada | 13 | 2026-08-26 |
| DT-456 | Billing page shows wrong currency (5) | In review | urgent | Noor | 8 | 2026-08-29 |
| DT-457 | Keyboard trap in date picker (5) | Todo | low | Lin | 5 | 2026-09-01 |
| DT-458 | Webhook retries flood the queue (5) | Done | medium | Kai | 3 | 2026-09-04 |
| DT-459 | Onboarding checklist resets (5) | In progress | high | Sam | 2 | 2026-09-07 |
| DT-460 | Checkout fails for saved cards (6) | Backlog | urgent | Ada | 1 | 2026-09-10 |
| DT-461 | Search results miss archived projects (6) | In review | low | Noor | 13 | 2026-09-13 |
| DT-462 | Invite email lands in spam (6) | Todo | medium | Lin | 8 | 2026-09-16 |
| DT-463 | Dark mode contrast on charts (6) | Done | high | Kai | 5 | 2026-09-19 |
Recipe details
DataAdvancedIssuesFiltersSaved viewsAI
Issue tracker
An issue list filtered every way at once: a text query, chips with impact counts and nested AND/OR groups, saved views in a sidebar, the filter in the URL, and an assistant that proposes filters to review.
Source
Copy the whole example or lift the component compositions into your own app. Recipes are showcase examples, not registry blocks.
examples/recipes/issue-tracker.tsx
"use client";
import { useMemo, useState } from "react";
import {
Badge,
DataTable,
FilterAssistant,
FilterBar,
QueryInput,
SavedViewsMenu,
cn,
countFilterMatches,
createFilterPredicate,
createHistoryFilterStore,
createMemoryFilterStore,
useFilterAssistant,
useFilterUrlState,
useSavedViews,
type BadgeTone,
type DataTableColumnDef,
type Filter,
} from "@dethink/components";
import type { RecipePreviewProps } from "@/lib/recipe-presentation";
import {
initialIssueViews,
issueAssistant,
issueEvaluateOptions,
issueFields,
issues,
type Issue,
type IssueStatus,
} from "./issue-tracker-data";
const statusTone: Record<IssueStatus, BadgeTone> = {
backlog: "neutral",
todo: "info",
"in-progress": "warning",
"in-review": "primary",
done: "success",
};
const statusLabel: Record<IssueStatus, string> = {
backlog: "Backlog",
todo: "Todo",
"in-progress": "In progress",
"in-review": "In review",
done: "Done",
};
const columns: DataTableColumnDef<Issue>[] = [
{
accessorKey: "key",
header: "Key",
cell: ({ row }) => (
<span className="font-mono text-xs whitespace-nowrap">
{row.original.key}
</span>
),
},
{ accessorKey: "title", header: "Title" },
{
accessorKey: "status",
header: "Status",
cell: ({ row }) => (
<Badge tone={statusTone[row.original.status]} variant="soft" size="sm">
{statusLabel[row.original.status]}
</Badge>
),
},
{
accessorKey: "priority",
header: "Priority",
cell: ({ row }) => (
<span className="capitalize">{row.original.priority}</span>
),
},
{ accessorKey: "assignee", header: "Assignee" },
{ accessorKey: "estimate", header: "Est." },
{
accessorKey: "updated",
header: "Updated",
cell: ({ row }) => (
<span className="whitespace-nowrap tabular-nums">
{row.original.updated}
</span>
),
},
];
export function IssueTrackerRecipe({
presentation = "embedded",
}: RecipePreviewProps) {
const fullPage = presentation === "full-page";
const [store] = useState(() =>
fullPage ? createHistoryFilterStore() : createMemoryFilterStore(),
);
const state = useFilterUrlState({ fields: issueFields, store });
const [views, setViews] = useState(initialIssueViews);
const [assistantOpen, setAssistantOpen] = useState(false);
const rowFilter = useMemo(
() =>
createFilterPredicate(state.filter, issueFields, issueEvaluateOptions),
[state.filter],
);
const count = useMemo(() => issues.filter(rowFilter).length, [rowFilter]);
const previewCount = useMemo(
() => async (filter: Filter) =>
countFilterMatches(issues, filter, issueFields, issueEvaluateOptions),
[],
);
const savedViews = useSavedViews({
state,
fields: issueFields,
views,
onCreate: (view) => setViews((current) => [...current, view]),
onUpdate: (view) =>
setViews((current) =>
current.map((item) => (item.id === view.id ? view : item)),
),
onDelete: (view) =>
setViews((current) => current.filter((item) => item.id !== view.id)),
});
const assistant = useFilterAssistant({
fields: issueFields,
state,
resolve: issueAssistant,
evaluateOptions: issueEvaluateOptions,
onPreviewCount: previewCount,
});
return (
<div
data-recipe-surface="issue-tracker"
className={cn(
"bg-muted/30 grid w-full min-w-0 gap-4 p-4 sm:p-6 lg:grid-cols-[13rem_minmax(0,1fr)]",
fullPage
? "min-h-[calc(100dvh-7rem)]"
: "border-border min-h-[42rem] rounded-xl border",
)}
>
<nav aria-label="Issue views" className="grid content-start gap-1">
<p className="text-muted-foreground px-2 pb-1 text-xs font-medium tracking-wide uppercase">
Views
</p>
<button
type="button"
aria-current={savedViews.activeViewId === null ? "page" : undefined}
onClick={() => {
savedViews.deselect();
state.clear();
}}
className="hover:bg-muted focus-visible:ring-ring aria-[current=page]:bg-muted rounded-md px-2 py-1.5 text-start text-sm outline-none focus-visible:ring-2 aria-[current=page]:font-medium"
>
All issues
</button>
{savedViews.views.map((view) => {
const active = view.id === savedViews.activeViewId;
return (
<button
key={view.id}
type="button"
aria-current={active ? "page" : undefined}
onClick={() => {
savedViews.apply(view.id);
}}
className="hover:bg-muted focus-visible:ring-ring aria-[current=page]:bg-muted flex items-center justify-between gap-2 rounded-md px-2 py-1.5 text-start text-sm outline-none focus-visible:ring-2 aria-[current=page]:font-medium"
>
<span className="min-w-0 truncate">{view.name}</span>
{active && savedViews.isDirty ? (
<span className="text-muted-foreground text-xs">edited</span>
) : null}
</button>
);
})}
</nav>
<div className="grid min-w-0 content-start gap-3">
<header className="flex flex-wrap items-center justify-between gap-3">
<div className="flex items-baseline gap-2">
<h2 className="text-xl font-semibold tracking-tight">Issues</h2>
<span className="text-muted-foreground text-sm tabular-nums">
{count} of {issues.length}
</span>
</div>
<div className="flex items-center gap-2">
<SavedViewsMenu
savedViews={savedViews}
scopes={["personal", "team"]}
/>
<button
type="button"
aria-expanded={assistantOpen}
aria-controls="issues-assistant"
onClick={() => {
setAssistantOpen((open) => !open);
}}
className="border-border bg-background hover:bg-muted focus-visible:ring-ring aria-expanded:bg-muted inline-flex h-8 items-center rounded-md border px-3 text-sm font-medium outline-none focus-visible:ring-2"
>
Ask in words
</button>
</div>
</header>
<QueryInput
fields={issueFields}
state={state}
defaultField="title"
labels={{
input: "Issue query",
placeholder: "status:!done assignee:ada labels:bug or plain words",
}}
/>
<div id="issues-assistant" hidden={!assistantOpen}>
{assistantOpen ? (
<FilterAssistant
assistant={assistant}
labels={{ placeholder: "e.g. my urgent bugs updated this week" }}
/>
) : null}
</div>
<DataTable
aria-label="Issues"
columns={columns}
data={issues}
getRowId={(row) => row.key}
rowFilter={rowFilter}
emptyContent="No issues match these filters."
toolbar={
<FilterBar
fields={issueFields}
state={state}
data={issues}
evaluateOptions={issueEvaluateOptions}
showImpact
addShortcut="f"
collapseAfter={4}
aria-label="Issue filters"
/>
}
/>
</div>
</div>
);
}