commits in the last year
Don't waste time on Stripe subscriptions or designing a pricing section. Get started today with our battle-tested stack and built-in monetization features.
Get Lifetime Access
525 commits in the last year
© 2026 Dirstarter. All rights reserved.
Dirstarter now emits domain events (tool submitted, tool published, tier upgraded, report created, and more) and can deliver them to an outbound webhook alongside the existing email notifications.
Set WEBHOOK_URL and every event starts POSTing a small JSON envelope to your endpoint. Wire it to n8n, Zapier, Make, or anything that speaks HTTP to automate whatever you like: share newly published tools on socials, ping Slack on a new report, sync to a CRM. It's off by default, and an optional WEBHOOK_TOKEN secures the receiver.
Because each event says plainly what happened and to whom, you can point the webhook straight at an AI agent node in n8n or Make and let it take it from there. Publish a tool and have the agent draft and post the social announcement. Get a report and let it triage the tool and write you a summary. Onboard a submitter with a personal follow-up. No glue code, just a prompt.
See the Webhooks documentation for the payload shape, the full event list, and how to add your own.
A proper navigation menu on desktop and a full-screen panel on mobile, both driven by one config file
The header outgrew its dropdowns. As the site gained collections, resources, and more content pages, a flat row of links stopped scaling. The navigation has been rebuilt on both breakpoints, driven by a single config.
Top-level groups can now open as a two-column menu: links on the left, a promotional panel on the right pointing at whatever you want to push: a hub page, the submit form, an upgrade.

Built on Base UI's navigation menu, so it's keyboard-navigable and screen-reader friendly out of the box. The popup morphs between groups instead of closing and reopening, and links highlight when you're on the current route.
Mobile gets a full-screen panel instead of a cramped dropdown. Groups are rendered as titled sections, and it closes on Escape and on every route change.

Both menus render from config/navigation.ts. A group is a label, a list of entries, and optional menu and promo settings:
Curated listings like Latest, Featured, and Coming Soon, each defined by a single config entry
Directories accumulate tools faster than visitors can browse them. Collections are curated listings, saved filters over your tool query, that give people a shortcut to the slice they care about, and give you more pages to rank.

Each collection gets its own page with the full listing UI: search, filters, sorting, and pagination, all scoped to that collection.

A collection is one entry in config/collections.ts:
The separate admin and dashboard areas are now one permission-gated /app workspace
The admin panel and the user dashboard have merged into a single /app workspace. Instead of two separate areas, everyone signs in to one dashboard that adapts to their role: admins manage content, while every signed-in user gets their own submissions and bookmarks.
/app: Content management, settings, and personal pages live under a single shell, and the sidebar shows only what your role can accesscan(user, permission)) decides what each user sees, replacing the hard split between the admin and dashboard areas/admin/* and /dashboard/* 308-redirect to their /app/* equivalents, so existing bookmarks and external links stay validExisting routes redirect automatically, so there's nothing to change in your deployment. Just sign in at /app.
All UI components have been migrated from Radix UI to Base UI for a more modern, flexible foundation
We've migrated all UI components from Radix UI to Base UI — a lower-level, unstyled component library that gives you more control over rendering and styling.
asChild prop has been replaced with Base UI's render prop and useRender hook for polymorphic renderingtailwind-variants instead of cva, with support for multi-element slot stylingdata-open, data-horizontal, etc.) instead of Radix's data-state attributesIf you've customized any UI components, you'll need to update:
asChild → render prop — Components no longer accept asChild. Use the render prop with a callback insteaddata-state="open" with data-open, and similar Base UI attribute conventionstailwind-variants syntax. Check for updated patterns{
labelKey: "navigation.resources",
menu: true,
items: [{ href: "/blog", labelKey: "navigation.blog", icon: entityIcons.blog }],
promo: {
href: "/submit",
titleKey: "navigation.menu.promo_resources_title",
descKey: "navigation.menu.promo_resources_description",
Drop menu: true and the group's links render flat in the header instead. Labels are i18n keys resolved at render, so navigation translates with the rest of the site. Change a link once and it updates on desktop, mobile, and anywhere else the config is read.
{
slug: "featured",
icon: SparklesIcon,
where: { tier: { not: ToolTier.Free } },
options: { enableFilters: true },
nav: true,
}Add the entry and the collection gets a page at /collections/[slug], a card on the /collections index, a header navigation slot, a footer link, and a sitemap entry. No other file needs touching.
where: merged into the public tool query, so any Prisma filter works: tier, status, category, dates, whatever you indexoverrideParams: force a sort or filter, e.g. oldest-first for a "Coming Soon" listingoptions: toggle the search, filter, and sort controls per collectionnav and badge: surface the collection in the header menu, optionally with a "New" labelCopy comes from your message files under pages.collections.<slug>, so titles, descriptions, and metadata stay translatable alongside the rest of the site.
Three collections ship by default: Latest, Featured, and Coming Soon. Delete the ones you don't need and add your own.
components/common/@radix-ui/react-slot — Use the new slot() utility from lib/slot.ts backed by Base UI's mergeProps