docs: complete project research

This commit is contained in:
2026-04-03 22:14:27 +02:00
parent 642ae0d43f
commit 443802fc68
5 changed files with 1642 additions and 1113 deletions

View File

@@ -1,198 +1,260 @@
# Stack Research -- v1.3 Research & Decision Tools
# Stack Research
**Project:** GearBox
**Researched:** 2026-03-16
**Scope:** Stack additions for side-by-side candidate comparison, setup impact preview, and drag-to-reorder candidate ranking with pros/cons
**Confidence:** HIGH
**Domain:** Multi-user gear management platform (v2.0 platform additions)
**Researched:** 2026-04-03
**Confidence:** MEDIUM-HIGH
## Key Finding: Zero New Dependencies
This document covers ONLY the new stack additions for v2.0. The existing stack (React 19, Hono, Drizzle ORM, TanStack Router/Query, Tailwind CSS v4, Lucide React, Recharts, framer-motion, Zustand, Zod, Bun) is validated and unchanged.
All three v1.3 features are achievable with the existing stack. The drag-to-reorder feature, which would normally require a dedicated DnD library, is covered by `framer-motion`'s built-in `Reorder` component — already installed at v12.37.0 with React 19 support confirmed.
## Recommended Stack
## Recommended Stack: Existing Technologies Only
### Authentication -- Logto (Self-Hosted)
### No New Dependencies Required
| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| Logto OSS | v1.36+ | External OIDC/OAuth 2.1 auth provider | TypeScript-native, purpose-built for app auth (not enterprise IAM), requires Postgres (shared infra), beautiful pre-built sign-in UI, React SDK with hooks, lightweight JWT validation on backend. MIT-licensed core. |
| @logto/react | ^4.0.13 | React SDK for auth flows | LogtoProvider wraps app, provides useLogto() hook for sign-in/sign-out/token access. Handles OIDC redirect flow, token refresh, and user info. |
| jose | ^6.2.2 | JWT validation on Hono backend | Zero-dependency, Bun-compatible, used to verify Logto-issued access tokens via JWKS. Recommended by Logto docs over heavier alternatives. |
| Feature | Library Needed | Status |
|---------|---------------|--------|
| Side-by-side comparison view | None — pure layout/UI | Existing Tailwind CSS |
| Setup impact preview | None — SQL delta calculation | Existing Drizzle ORM + TanStack Query |
| Drag-to-reorder candidates | `Reorder` component | Already in `framer-motion@12.37.0` |
| Pros/cons text fields | None — schema + form | Existing Drizzle + Zod + React |
**Why Logto over alternatives:**
### How Each Feature Uses the Existing Stack
| Provider | Why Not |
|----------|---------|
| Authentik | Python-based, heavyweight (designed for enterprise proxy/SSO), overkill for app-level auth. No React SDK -- requires raw OIDC integration. Better for infra-level SSO (Portainer, Grafana). |
| Zitadel | Go-based, Kubernetes-first architecture, AGPL 3.0 license (copyleft since 2025). Stronger for multi-tenant B2B SaaS. Over-engineered for a single-product platform. |
| SuperTokens | Session-based by default (not OIDC), requires embedding their middleware into your backend. Tighter coupling than external provider model. |
| Keycloak | Java-based, heavy memory footprint (1-2GB RAM), complex admin UI. Industry standard but vastly over-scoped for this use case. |
#### 1. Side-by-Side Candidate Comparison
**Integration pattern:** Logto runs as a separate Docker container alongside Postgres. React app redirects to Logto's hosted sign-in page for auth flows. Hono backend validates JWT access tokens from the Authorization header using `jose` JWKS verification -- no Logto SDK needed on the backend, just standard OIDC token validation. User identity is the Logto `sub` claim (a stable string ID), stored as `userId` on all user-owned records.
**No schema changes. No new dependencies.**
| Existing Tech | How It Is Used |
|---------------|----------------|
| Tailwind CSS v4 | Responsive comparison table layout. Horizontal scroll on mobile with `overflow-x-auto`. Fixed first column (row labels) using `sticky left-0`. |
| TanStack Query (`useThread`) | Thread detail already fetches all candidates in one query. Comparison view reads from the same cached data — no new API endpoint. |
| Lucide React | Comparison row icons (weight, price, status, link). Already in the curated icon set. |
| `formatWeight` / `formatPrice` | Existing formatters handle display with selected unit/currency. No changes needed. |
| `useWeightUnit` / `useCurrency` | Existing hooks provide formatting context. Comparison view uses them identically to `CandidateCard`. |
**Implementation approach:** Add a view toggle (grid vs. comparison table) to the thread detail page. The comparison view is a `<table>` or CSS grid with candidates as columns and attributes as rows. Data already lives in `useThread` response — no API changes.
#### 2. Setup Impact Preview
**No new dependencies. Requires one new API endpoint.**
| Existing Tech | How It Is Used |
|---------------|----------------|
| Drizzle ORM | New query: for a given setup, sum `weight_grams` and `price_cents` of its items. Then compute delta against each candidate's `weight_grams` and `price_cents`. Pure arithmetic in the service layer. |
| TanStack Query | New `useSetupImpact(threadId, setupId)` hook fetching `GET /api/threads/:threadId/impact?setupId=X`. Returns array of `{ candidateId, weightDelta, costDelta }`. |
| Hono + Zod validator | New route validates `setupId` query param. Delegates to service function. |
| `formatWeight` / `formatPrice` | Format deltas with `+` prefix for positive values (candidate adds weight/cost) and `-` for negative (candidate is lighter/cheaper than what's already in setup). |
| `useSetups` hook | Existing `useSetups()` provides the setup list for the picker dropdown. |
**Delta calculation logic (server-side service):**
**Backend middleware pattern (Hono):**
```typescript
// For each candidate in thread:
// weightDelta = candidate.weightGrams - (matching item in setup).weightGrams
// If no matching item in setup (it would be added, not replaced): delta = candidate.weightGrams
import { createRemoteJWKSet, jwtVerify } from "jose";
// A "matching item" means: item in setup with same categoryId as the thread.
// This is the intended semantic: "how does picking this candidate affect my setup?"
```
**Key decision:** Impact preview is read-only and derived. It does not mutate any data. It computes what *would* happen if the candidate were picked, without modifying the setup. The delta is displayed inline on each candidate card or in the comparison view.
#### 3. Drag-to-Reorder Candidate Ranking with Pros/Cons
**No new DnD library. Requires schema changes.**
| Existing Tech | How It Is Used |
|---------------|----------------|
| `framer-motion@12.37.0` `Reorder` | `Reorder.Group` wraps the candidate list. `Reorder.Item` wraps each candidate card. `onReorder` updates local order state. `onDragEnd` fires the persist mutation. |
| Drizzle ORM | Two new columns on `thread_candidates`: `sortOrder integer` (default 0, lower = higher rank) and `pros text` / `cons text` (nullable). |
| TanStack Query mutation | `usePatchCandidate` for pros/cons text updates. `useReorderCandidates` for bulk sort order update after drag-end. |
| Hono + Zod validator | `PATCH /api/threads/:threadId/candidates/reorder` accepts `{ candidates: Array<{ id, sortOrder }> }`. `PATCH /api/candidates/:id` accepts `{ pros?, cons? }`. |
| Zod | Extend `updateCandidateSchema` with `pros: z.string().nullable().optional()`, `cons: z.string().nullable().optional()`, `sortOrder: z.number().int().optional()`. |
**Framer Motion `Reorder` API pattern:**
```typescript
import { Reorder } from "framer-motion";
// State holds candidates sorted by sortOrder
const [orderedCandidates, setOrderedCandidates] = useState(
[...candidates].sort((a, b) => a.sortOrder - b.sortOrder)
const jwks = createRemoteJWKSet(
new URL("https://logto.example.com/oidc/jwks")
);
// onReorder fires continuously during drag — update local state only
// onDragEnd fires once on drop — persist to DB
<Reorder.Group
axis="y"
values={orderedCandidates}
onReorder={setOrderedCandidates}
>
{orderedCandidates.map((candidate) => (
<Reorder.Item
key={candidate.id}
value={candidate}
onDragEnd={() => persistOrder(orderedCandidates)}
>
<CandidateCard ... />
</Reorder.Item>
))}
</Reorder.Group>
const authMiddleware = createMiddleware(async (c, next) => {
const token = c.req.header("Authorization")?.replace("Bearer ", "");
if (!token) return c.json({ error: "Unauthorized" }, 401);
const { payload } = await jwtVerify(token, jwks, {
issuer: "https://logto.example.com/oidc",
audience: "your-api-resource-indicator",
});
c.set("userId", payload.sub);
await next();
});
```
**Schema changes required:**
**React provider pattern:**
| Table | Column | Type | Default | Purpose |
|-------|--------|------|---------|---------|
| `thread_candidates` | `sort_order` | `integer NOT NULL` | `0` | Rank position (lower = higher rank) |
| `thread_candidates` | `pros` | `text` | `NULL` | Free-text pros annotation |
| `thread_candidates` | `cons` | `text` | `NULL` | Free-text cons annotation |
```typescript
import { LogtoProvider, LogtoConfig } from "@logto/react";
**Sort order persistence pattern:** On drag-end, send the full reordered array with new `sortOrder` values (0-based index positions). Backend replaces existing `sort_order` values atomically. This is the same delete-all + re-insert pattern used for `setupItems` but as an UPDATE instead.
const config: LogtoConfig = {
endpoint: "https://logto.example.com",
appId: "<your-app-id>",
resources: ["https://api.gearbox.example.com"],
};
// Wrap app root
<LogtoProvider config={config}>
<App />
</LogtoProvider>
```
### Database -- PostgreSQL via Bun Native Driver
| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| PostgreSQL | 16+ | Primary database | Required by Logto anyway, proper concurrent access for multi-user, JSONB for flexible spec fields, full-text search for discovery feed. |
| drizzle-orm | ^0.45.1 (existing) | Type-safe ORM | Already in use. Switch from `drizzle-orm/bun-sqlite` to `drizzle-orm/bun-sql` for Postgres. Schema definitions move from `sqlite-core` to `pg-core`. |
| Bun native SQL | built-in | Postgres driver | Zero additional dependencies. `import { SQL } from "bun"` provides native Postgres bindings. Drizzle ORM supports it via `drizzle-orm/bun-sql`. |
| postgres (postgres.js) | ^3.4.8 | Fallback Postgres driver | Only needed if Bun native SQL has issues with drizzle-kit CLI tooling (known issue #4122). More mature ecosystem, proven with Drizzle. Install as dev dependency for drizzle-kit. |
**Schema migration approach:**
1. Rewrite `src/db/schema.ts` imports from `drizzle-orm/sqlite-core` to `drizzle-orm/pg-core`
2. Replace `sqliteTable` with `pgTable`
3. Replace `integer().primaryKey({ autoIncrement: true })` with `integer().primaryKey().generatedAlwaysAsIdentity()` for PKs
4. Replace `integer("created_at", { mode: "timestamp" })` with `timestamp("created_at").defaultNow().notNull()`
5. Add `userId text("user_id").notNull()` to all user-owned tables (items, threads, setups, categories)
6. Add `visibility text("visibility").notNull().default("private")` to setups and profiles
7. Generate fresh Postgres migration with `drizzle-kit generate`
8. Write a one-time data migration script (SQLite read -> Postgres insert) for existing data
**drizzle.config.ts change:**
```typescript
// Before
{ dialect: "sqlite", dbCredentials: { url: "./gearbox.db" } }
// After
{ dialect: "postgresql", dbCredentials: { url: process.env.DATABASE_URL } }
```
**Known issue:** drizzle-kit CLI does not use the Bun SQL driver for `push`/`generate` commands (GitHub issue #4122). Workaround: install `postgres` (postgres.js) as a dev dependency for drizzle-kit, while the app runtime uses Bun native SQL.
### Image Storage -- Bun Native S3 + MinIO
| Technology | Version | Purpose | Why Recommended |
|------------|---------|---------|-----------------|
| Bun S3Client | built-in | S3 API client | Zero dependencies, native Bun bindings, extends Blob interface. Supports presigned URLs, streaming uploads. Built-in MinIO compatibility. |
| MinIO | latest | Self-hosted S3-compatible object storage | Replaces local `./uploads/` directory. Single Go binary, Docker-friendly, S3 API compatible. Handles multi-user image scaling without cloud vendor lock-in. |
**Why Bun native S3 over @aws-sdk/client-s3:**
- Zero additional dependencies (Bun ships with it)
- Simpler API (extends Blob, web-standard patterns)
- Native performance bindings
- Full MinIO compatibility documented by Bun team
**Migration from ./uploads/:**
1. Deploy MinIO container alongside app
2. Create `gearbox-images` bucket
3. Write migration script to upload existing files from `./uploads/` to MinIO
4. Update image service to use S3Client for reads/writes
5. Serve images via presigned URLs or a proxy route on Hono
**Configuration:**
```typescript
import { S3Client } from "bun";
const storage = new S3Client({
accessKeyId: process.env.S3_ACCESS_KEY!,
secretAccessKey: process.env.S3_SECRET_KEY!,
bucket: "gearbox-images",
endpoint: process.env.S3_ENDPOINT!, // e.g., http://minio:9000
});
```
### Supporting Libraries
| Library | Version | Purpose | When to Use |
|---------|---------|---------|-------------|
| jose | ^6.2.2 | JWKS-based JWT verification | Every authenticated API request -- validate Logto access tokens on Hono middleware |
| @logto/react | ^4.0.13 | React auth provider + hooks | Wrap app root, sign-in/sign-out flows, access token retrieval for API calls |
### Development / Infrastructure
| Tool | Purpose | Notes |
|------|---------|-------|
| Docker Compose | Local dev environment | Postgres + Logto + MinIO containers. App still runs on bare Bun for HMR. |
| drizzle-kit | Schema management | Same tool, different dialect config. `bun run db:generate` and `bun run db:push` still work. |
## Installation
```bash
# No new packages. Zero.
```
# New production dependencies
bun add @logto/react jose
All required capabilities are already installed.
# New dev dependencies (for drizzle-kit Postgres support)
bun add -D postgres
# No install needed for:
# - Bun native S3 (built-in)
# - Bun native SQL/Postgres (built-in)
# - drizzle-orm (already installed, just change imports)
```
## Alternatives Considered
### Drag and Drop: Why Not Add a Dedicated Library?
### Authentication Provider
| Option | Version | React 19 Status | Verdict |
|--------|---------|-----------------|---------|
| `framer-motion` Reorder (already installed) | 12.37.0 | React 19 explicit peerDep (`^18.0.0 || ^19.0.0`) | USE THIS |
| `@dnd-kit/core` + `@dnd-kit/sortable` | 6.3.1 | No React 19 support (stale ~1yr, open GitHub issue #1511) | AVOID |
| `@dnd-kit/react` (new rewrite) | 0.3.2 | React 19 compatible | Pre-1.0, no maintainer ETA on stable |
| `@hello-pangea/dnd` | 18.0.1 | No React 19 (stale ~1yr, peerDep `^18.0.0` only) | AVOID |
| `pragmatic-drag-and-drop` | latest | Core is React-agnostic but some sub-packages missing React 19 | Overkill for a single sortable list |
| Custom HTML5 DnD | N/A | N/A | 200+ lines of boilerplate, worse accessibility |
| Recommended | Alternative | When to Use Alternative |
|-------------|-------------|-------------------------|
| Logto | Authentik | If you need proxy-mode SSO for non-OIDC apps (Portainer, legacy tools) |
| Logto | Zitadel | If building multi-tenant B2B SaaS with organization-level isolation |
| Logto | Keycloak | If enterprise LDAP/AD integration is mandatory |
**Why framer-motion `Reorder` wins:** Already in the bundle. React 19 peer dep confirmed in lockfile. Handles the single use case (vertical sortable list) with 10 lines of code. Provides smooth layout animations at zero additional cost. The limitation (no cross-container drag, no multi-row grid) does not apply — candidate ranking is a single vertical list.
### Database Driver
**Why not `@dnd-kit`:** The legacy `@dnd-kit/core@6.3.1` has no official React 19 support and has been unmaintained for ~1 year. The new `@dnd-kit/react@0.3.2` does support React 19 but is pre-1.0 with zero maintainer response on stability/roadmap questions (GitHub Discussion #1842 has 0 replies). Adding a pre-1.0 library when the project already has a working solution is unjustifiable.
| Recommended | Alternative | When to Use Alternative |
|-------------|-------------|-------------------------|
| Bun native SQL (`bun:sql`) | postgres.js | If Bun native SQL has concurrency bugs (known issue in Bun 1.2.0 with concurrent statements) |
| Bun native SQL (`bun:sql`) | @neondatabase/serverless | If deploying to serverless/edge where persistent connections are not possible |
### Setup Impact: Why Not Client-Side Calculation?
### Image Storage
Client-side delta calculation (using cached React Query data) is simpler to implement but:
- Requires loading both the full setup items list AND all candidates into the client
- Introduces staleness bugs if setup items change in another tab
- Is harder to test (service test vs. component test)
Server-side calculation in a service function is testable, authoritative, and consistent with the existing architecture (services compute aggregates, not components).
| Recommended | Alternative | When to Use Alternative |
|-------------|-------------|-------------------------|
| MinIO (self-hosted) | Cloudflare R2 | If you want zero-ops storage with no egress fees and don't mind cloud dependency |
| MinIO (self-hosted) | Local filesystem (current) | For development/testing only. Not viable for multi-user at scale. |
## What NOT to Add
| Avoid | Why | Use Instead |
|-------|-----|-------------|
| `@dnd-kit/core` + `@dnd-kit/sortable` | No React 19 support, stale for ~1 year (latest 6.3.1 from 2024) | `framer-motion` Reorder (already installed) |
| `@hello-pangea/dnd` | No React 19 support, peerDep `react: "^18.0.0"` only, stale | `framer-motion` Reorder |
| `react-comparison-table` or similar component packages | Fragile third-party layouts for a simple table. Custom Tailwind table is trivial and design-consistent. | Custom Tailwind CSS table layout |
| Modal/dialog library (Radix, Headless UI) | The project already has a hand-rolled modal pattern (`SlideOutPanel`, `ConfirmDialog`). Adding a library for one more dialog adds inconsistency. | Extend existing modal patterns |
| Rich text editor for pros/cons | Markdown editors are overkill for a single-line annotation field. Users want a quick note, not a document. | Plain `<textarea>` with Tailwind styling |
| @aws-sdk/client-s3 | 60+ transitive dependencies, Bun has native S3 support | Bun built-in S3Client |
| passport.js / express-session | Wrong paradigm -- we want external OIDC, not embedded session auth | Logto + jose JWT validation |
| next-auth / auth.js | Designed for Next.js, assumes framework integration we don't have | Logto (external provider) |
| better-auth | Embedded auth library, opposite of external provider model | Logto (external provider) |
| pg (node-postgres) | Callback-based API, Bun has native Postgres bindings | Bun native SQL or postgres.js |
| sharp / image processing libs | Premature optimization -- serve originals first, add resizing later if needed | Direct S3 storage of originals |
| Redis | Not needed at this scale. Postgres handles sessions (via Logto), caching is premature | Postgres for everything |
| Prisma | Already using Drizzle ORM, no reason to add a second ORM | drizzle-orm (existing) |
| nanoid / cuid2 | Postgres `gen_random_uuid()` is built-in for public-facing IDs if needed | Postgres native UUID generation |
| TypeORM / Sequelize | Legacy ORMs with worse TypeScript support than Drizzle | drizzle-orm (existing) |
## Stack Patterns by Variant
## Infrastructure Architecture
**If the comparison view needs mobile scroll:**
- Wrap comparison table in `overflow-x-auto`
- Freeze the first column (attribute labels) with `sticky left-0 bg-white z-10`
- This is pure CSS, no JavaScript or library needed
```
Docker Compose (dev) / Docker (prod)
+-- gearbox-app (Bun, port 3000)
+-- gearbox-postgres (PostgreSQL 16, port 5432)
| +-- gearbox DB (app data)
| +-- logto DB (Logto data, separate database same instance)
+-- gearbox-logto (Logto OSS, port 3001 app / 3002 admin)
+-- gearbox-minio (MinIO, port 9000 API / 9001 console)
```
**If the setup impact preview needs a setup picker:**
- Use `useSetups()` (already exists) to populate a `<select>` dropdown
- Store selected setup ID in local component state (not URL params — this is transient UI)
- No new state management needed
**If pros/cons fields need to auto-save:**
- Use a debounced mutation (300-500ms) that fires on `onChange`
- Or save on `onBlur` (simpler, adequate for this use case)
- Existing `useUpdateCandidate` hook already handles candidate mutations — extend schema only
Logto and the app share a single Postgres instance (different databases). This keeps infrastructure simple -- one Postgres to back up, one to monitor. Logto requires PostgreSQL 14+; using 16 covers both.
## Version Compatibility
| Package | Version in Project | React 19 Compatible | Notes |
|---------|-------------------|---------------------|-------|
| `framer-motion` | 12.37.0 | YES — peerDeps `"^18.0.0 || ^19.0.0"` confirmed in lockfile | `Reorder` component available since v5 |
| `drizzle-orm` | 0.45.1 | N/A (server-side) | ALTER TABLE or migration for new columns |
| `zod` | 4.3.6 | N/A | Extend existing schemas |
| `@tanstack/react-query` | 5.90.21 | YES | New hooks follow existing patterns |
| Package | Compatible With | Notes |
|---------|-----------------|-------|
| drizzle-orm@0.45.x | Bun native SQL | Supported via `drizzle-orm/bun-sql` driver |
| drizzle-orm@0.45.x | postgres.js@3.4.x | Supported via `drizzle-orm/postgres-js` driver (fallback) |
| drizzle-kit@0.31.x | PostgreSQL 16 | Generates Postgres-dialect migrations |
| @logto/react@4.x | React 19 | Uses React context/hooks, compatible |
| jose@6.x | Bun runtime | Explicitly lists Bun support in docs |
| Logto OSS v1.36 | PostgreSQL 14+ | Logto requires PG 14 minimum; use PG 16 for both app and Logto |
| Bun S3Client | MinIO latest | Documented compatibility with endpoint configuration |
## Migration Checklist (SQLite to Postgres)
1. **Schema rewrite**: `sqlite-core` -> `pg-core` imports, adjust column types
2. **Driver swap**: `drizzle-orm/bun-sqlite` -> `drizzle-orm/bun-sql`
3. **Config update**: `drizzle.config.ts` dialect and credentials
4. **Fresh migrations**: Generate from scratch for Postgres (do not try to convert SQLite migrations)
5. **Data migration**: One-time script reads SQLite, writes to Postgres
6. **Test infrastructure**: Update `createTestDb()` helper to use Postgres test database (or pg-mem for in-memory testing)
7. **CI pipeline**: Add Postgres service container for test runs
8. **Remove SQLite deps**: Remove `better-sqlite3` from devDependencies after migration confirmed
## Sources
- [framer-motion package lockfile entry] — peerDeps `react: "^18.0.0 || ^19.0.0"` confirmed (HIGH confidence, from project's `bun.lock`)
- [Motion Reorder docs](https://motion.dev/docs/react-reorder) — `Reorder.Group`, `Reorder.Item`, `useDragControls` API, `onDragEnd` pattern for persisting order (HIGH confidence)
- [Motion Changelog](https://motion.dev/changelog) — v12.37.0 actively maintained through Feb 2026 (HIGH confidence)
- [@dnd-kit/core npm](https://www.npmjs.com/package/@dnd-kit/core) — v6.3.1, last published ~1 year ago, no React 19 support (HIGH confidence)
- [dnd-kit React 19 issue #1511](https://github.com/clauderic/dnd-kit/issues/1511) — CLOSED but React 19 TypeScript issues confirmed (MEDIUM confidence)
- [@dnd-kit/react roadmap discussion #1842](https://github.com/clauderic/dnd-kit/discussions/1842) — 0 maintainer replies on stability question (HIGH confidence — signals pre-1.0 risk)
- [hello-pangea/dnd React 19 issue #864](https://github.com/hello-pangea/dnd/issues/864) — React 19 support still open as of Jan 2026 (HIGH confidence)
- [Top 5 Drag-and-Drop Libraries for React 2026](https://puckeditor.com/blog/top-5-drag-and-drop-libraries-for-react) — ecosystem overview confirming dnd-kit and hello-pangea/dnd limitations (MEDIUM confidence)
- [Logto official docs -- React quickstart](https://docs.logto.io/quick-starts/react) -- SDK setup, LogtoProvider config (HIGH confidence)
- [Logto API protection -- JWT validation](https://docs.logto.io/api-protection/nodejs/express) -- jose-based middleware pattern (HIGH confidence)
- [Logto OSS getting started](https://docs.logto.io/logto-oss/get-started-with-oss) -- Docker deployment, Postgres requirements (HIGH confidence)
- [Logto @logto/react npm](https://www.npmjs.com/package/@logto/react) -- Version 4.0.13 confirmed (HIGH confidence)
- [Drizzle ORM -- Bun SQL driver](https://orm.drizzle.team/docs/connect-bun-sql) -- Native Postgres via Bun (HIGH confidence)
- [Drizzle ORM -- PostgreSQL column types](https://orm.drizzle.team/docs/column-types/pg) -- pg-core schema definitions (HIGH confidence)
- [drizzle-kit Bun SQL issue #4122](https://github.com/drizzle-team/drizzle-orm/issues/4122) -- Known CLI limitation with Bun driver (MEDIUM confidence)
- [Bun S3 documentation](https://bun.com/docs/runtime/s3) -- Native S3 client, MinIO config (HIGH confidence)
- [MinIO GitHub](https://github.com/minio/minio) -- S3-compatible self-hosted storage (HIGH confidence)
- [jose GitHub](https://github.com/panva/jose) -- JWT library v6.2.2, explicit Bun support (HIGH confidence)
- [Authentik vs Zitadel comparison](https://wz-it.com/en/blog/authentik-vs-zitadel-identity-provider-comparison/) -- Auth provider analysis (MEDIUM confidence)
- [Keycloak vs Authentik vs Zitadel 2026](https://blog.houseoffoss.com/post/keycloak-vs-authentik-vs-zitadel-2026-which-open-source-login-tool-should-you-use) -- Ecosystem overview (MEDIUM confidence)
- [postgres.js npm](https://www.npmjs.com/package/postgres) -- Version 3.4.8, fallback driver (HIGH confidence)
---
*Stack research for: GearBox v1.3 -- Research & Decision Tools*
*Researched: 2026-03-16*
*Stack research for: GearBox v2.0 Platform Foundation*
*Researched: 2026-04-03*