diff --git a/.gitignore b/.gitignore
index b6f7b80..f02bb85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,6 @@ packages/shared/drizzle/meta/
 # Task Master logs
 logs/
 events.jsonl
+# Orchestration artifacts
+.worktrees/
+.claude_settings.json
diff --git a/.taskmaster/config.json b/.taskmaster/config.json
index 712b04b..3df642f 100644
--- a/.taskmaster/config.json
+++ b/.taskmaster/config.json
@@ -22,23 +22,8 @@
   "global": {
     "logLevel": "info",
     "debug": false,
-    "defaultNumTasks": 10,
     "defaultSubtasks": 5,
     "defaultPriority": "medium",
-    "projectName": "TaskMaster",
-    "ollamaBaseURL": "http://localhost:11434/api",
-    "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
-    "responseLanguage": "English",
-    "enableCodebaseAnalysis": true,
-    "enableProxy": false,
-    "anonymousTelemetry": true,
-    "userId": "1234567890"
-  },
-  "claudeCode": {},
-  "codexCli": {},
-  "grokCli": {
-    "timeout": 120000,
-    "workingDirectory": null,
-    "defaultModel": "grok-4-latest"
+    "projectName": "TaskMaster"
   }
 }
\ No newline at end of file
diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json
index 43b2c23..ac0f4ce 100644
--- a/.taskmaster/tasks/tasks.json
+++ b/.taskmaster/tasks/tasks.json
@@ -2,14 +2,14 @@
   "master": {
     "tasks": [
       {
-        "id": 1,
+        "id": "1",
         "title": "Initialize Next.js Monorepo Project Structure",
         "description": "Set up the foundational Next.js project with TypeScript, Tailwind CSS, and Radix UI, establishing the monorepo structure for both the serverless core and worker packages.",
         "details": "Initialize the project using `npx create-next-app@latest` with TypeScript enabled. Configure the monorepo structure using npm workspaces or turborepo with three packages: `packages/web` (Next.js serverless core), `packages/worker` (BullMQ ETL worker), and `packages/daemon` (Hardware daemon). Install core dependencies: `tailwindcss`, `postcss`, `autoprefixer`, `@radix-ui/themes`, and `@radix-ui/react-*` components. Configure `tailwind.config.ts` with Radix UI preset integration. Create shared TypeScript configuration in the root `tsconfig.json` with path aliases. Set up ESLint and Prettier for consistent code style. Create `.env.example` with placeholders for all required environment variables: `DATABASE_URL`, `REDIS_URL`, `EASYPOST_API_KEY`, `MANAPOOL_WEBHOOK_SECRET`, `TCGPLAYER_USERNAME`, `TCGPLAYER_PASSWORD`, `SUPPORTED_CATEGORIES`, `CRON_TCGCSV_SYNC`, `CRON_TCGTRACKING_SYNC`.",
         "testStrategy": "Verify successful `npm install` and `npm run dev` execution. Confirm TypeScript compilation succeeds with no errors. Validate Tailwind CSS purge configuration by building production bundle and checking CSS output size. Test Radix UI component rendering by creating a simple Button component.",
         "priority": "high",
         "dependencies": [],
-        "status": "pending",
+        "status": "done",
         "subtasks": [
           {
             "id": 1,
@@ -18,7 +18,8 @@
             "dependencies": [],
             "details": "Run `npx create-next-app@latest packages/web --typescript --tailwind --eslint --app --src-dir --import-alias \"@/*\"` to scaffold the Next.js application. This creates the web package with TypeScript, App Router, and src directory structure. After creation, verify the generated files include: package.json with Next.js dependencies, tsconfig.json with proper compiler options, next.config.js for Next.js configuration, and the src/app directory structure. Remove any auto-generated example files that won't be needed.",
             "status": "pending",
-            "testStrategy": "Verify the packages/web directory exists with a valid package.json. Run `cd packages/web && npm run dev` to confirm the development server starts successfully on port 3000. Check that TypeScript compilation succeeds with `npm run build`."
+            "testStrategy": "Verify the packages/web directory exists with a valid package.json. Run `cd packages/web && npm run dev` to confirm the development server starts successfully on port 3000. Check that TypeScript compilation succeeds with `npm run build`.",
+            "parentId": "undefined"
           },
           {
             "id": 2,
@@ -29,7 +30,8 @@
             ],
             "details": "Create a root package.json with `\"workspaces\": [\"packages/*\"]` configuration. Install Turborepo with `npm install turbo --save-dev` at root. Create turbo.json with pipeline configuration for build, dev, lint, and test tasks defining proper task dependencies. Create empty package directories for packages/worker (BullMQ ETL worker), packages/daemon (Hardware daemon), and packages/shared (shared utilities). Initialize each package with its own package.json containing name scoped to @zman-tcg/[package-name]. Update packages/web/package.json name to @zman-tcg/web for consistency.",
             "status": "pending",
-            "testStrategy": "Run `npm install` from root and verify all workspaces are linked correctly. Run `npx turbo run build` to verify pipeline execution. Check that `npm run dev --workspace=@zman-tcg/web` starts the Next.js dev server."
+            "testStrategy": "Run `npm install` from root and verify all workspaces are linked correctly. Run `npx turbo run build` to verify pipeline execution. Check that `npm run dev --workspace=@zman-tcg/web` starts the Next.js dev server.",
+            "parentId": "undefined"
           },
           {
             "id": 3,
@@ -40,7 +42,8 @@
             ],
             "details": "Install Radix UI dependencies in packages/web: `npm install @radix-ui/themes @radix-ui/colors`. Configure tailwind.config.ts to extend the default theme with Radix UI color tokens and custom design system values. Update packages/web/src/app/globals.css to import Radix UI theme styles with `@import '@radix-ui/themes/styles.css'`. Wrap the root layout (src/app/layout.tsx) with Radix Theme provider: `<Theme accentColor=\"blue\" grayColor=\"slate\" radius=\"medium\">`. Install additional Radix primitives as needed: `@radix-ui/react-dialog`, `@radix-ui/react-dropdown-menu`, `@radix-ui/react-toast` for common UI patterns.",
             "status": "pending",
-            "testStrategy": "Create a test component using Radix UI Button and Theme tokens. Run the dev server and verify Radix UI styles render correctly. Build production bundle and verify CSS output includes Radix UI styles. Check that Tailwind utility classes work alongside Radix components."
+            "testStrategy": "Create a test component using Radix UI Button and Theme tokens. Run the dev server and verify Radix UI styles render correctly. Build production bundle and verify CSS output includes Radix UI styles. Check that Tailwind utility classes work alongside Radix components.",
+            "parentId": "undefined"
           },
           {
             "id": 4,
@@ -51,7 +54,8 @@
             ],
             "details": "Initialize packages/shared with package.json containing `\"name\": \"@zman-tcg/shared\"`, `\"main\": \"./src/index.ts\"`, and `\"types\": \"./src/index.ts\"`. Create shared tsconfig.base.json at root with common compiler options: strict mode, ES2022 target, moduleResolution: bundler, paths aliases for @zman-tcg/shared. Create packages/shared/tsconfig.json extending the base config. Set up packages/shared/src/index.ts as the main export file. Create initial type definitions for shared schemas (orders, products, skus) in packages/shared/src/types/. Configure each workspace tsconfig.json to extend root tsconfig.base.json and include proper path mappings.",
             "status": "pending",
-            "testStrategy": "Import a type from @zman-tcg/shared in packages/web and verify TypeScript resolves it correctly. Run `tsc --noEmit` in each workspace to verify no type errors. Test that IDE autocomplete works for shared imports."
+            "testStrategy": "Import a type from @zman-tcg/shared in packages/web and verify TypeScript resolves it correctly. Run `tsc --noEmit` in each workspace to verify no type errors. Test that IDE autocomplete works for shared imports.",
+            "parentId": "undefined"
           },
           {
             "id": 5,
@@ -62,7 +66,8 @@
             ],
             "details": "Create .env.example at the project root with all required environment variables organized by category: Database (`DATABASE_URL` for PostgreSQL connection string), Cache (`REDIS_URL` for Redis/BullMQ), Shipping (`EASYPOST_API_KEY`, `EASYPOST_TEST_MODE`), ManaPool Integration (`MANAPOOL_WEBHOOK_SECRET`, `MANAPOOL_API_URL`), TCGPlayer Integration (`TCGPLAYER_USERNAME`, `TCGPLAYER_PASSWORD`, `TCGPLAYER_STORE_ID`), Catalog (`SUPPORTED_CATEGORIES` as comma-separated list), Cron Schedules (`CRON_TCGCSV_SYNC`, `CRON_TCGTRACKING_SYNC`), App Config (`NODE_ENV`, `NEXT_PUBLIC_APP_URL`). Add .env* to .gitignore to prevent committing secrets. Install and configure dotenv for worker package. Add environment validation using zod or similar in packages/shared/src/config/env.ts.",
             "status": "pending",
-            "testStrategy": "Copy .env.example to .env.local and fill with test values. Verify Next.js loads environment variables correctly in dev mode. Test that missing required variables throw validation errors at startup."
+            "testStrategy": "Copy .env.example to .env.local and fill with test values. Verify Next.js loads environment variables correctly in dev mode. Test that missing required variables throw validation errors at startup.",
+            "parentId": "undefined"
           },
           {
             "id": 6,
@@ -73,144 +78,147 @@
             ],
             "details": "Install ESLint and Prettier dependencies at root: `npm install -D eslint prettier eslint-config-prettier eslint-plugin-prettier @typescript-eslint/eslint-plugin @typescript-eslint/parser eslint-plugin-react eslint-plugin-react-hooks`. Create .eslintrc.json at root extending recommended configs with TypeScript, React, and Prettier plugins. Configure rules for consistent imports, no unused variables (error level), and React hooks rules. Create .prettierrc with consistent formatting: semi: true, singleQuote: true, tabWidth: 2, trailingComma: 'es5'. Create .prettierignore and .eslintignore to exclude node_modules, .next, dist directories. Add lint and format scripts to root package.json: `\"lint\": \"turbo run lint\"`, `\"format\": \"prettier --write .\"`. Configure VS Code settings in .vscode/settings.json for format-on-save.",
             "status": "pending",
-            "testStrategy": "Run `npm run lint` and verify it checks all workspaces. Run `npm run format` and verify it formats files correctly. Introduce an intentional lint error and verify ESLint catches it. Test that Prettier and ESLint rules don't conflict."
+            "testStrategy": "Run `npm run lint` and verify it checks all workspaces. Run `npm run format` and verify it formats files correctly. Introduce an intentional lint error and verify ESLint catches it. Test that Prettier and ESLint rules don't conflict.",
+            "parentId": "undefined"
           }
-        ]
+        ],
+        "updatedAt": "2026-02-23T08:54:07.638Z"
       },
       {
-        "id": 2,
+        "id": "2",
         "title": "Configure PostgreSQL Database with Drizzle ORM",
         "description": "Set up PostgreSQL connection using a serverless-compatible provider (Neon/Supabase) and configure Drizzle ORM for type-safe database operations.",
         "details": "Install Drizzle ORM dependencies: `drizzle-orm`, `drizzle-kit`, `@neondatabase/serverless` (or `@supabase/supabase-js`), and `pg` for the worker. Create `packages/web/src/db/index.ts` to initialize the Drizzle client with serverless adapter. Configure `drizzle.config.ts` at the root with schema path, output directory for migrations, and database credentials from environment variables. Set up connection pooling configuration to handle serverless burst scenarios (max connections, idle timeout). Create a shared `packages/shared` package for database schema and types that both `web` and `worker` packages can import. Configure environment-specific connection strings for development (local PostgreSQL or Docker) and production (Neon/Supabase).",
         "testStrategy": "Run `drizzle-kit generate:pg` to verify schema generation. Execute a simple query to confirm connection. Test connection pooling by simulating concurrent requests. Verify TypeScript types are correctly inferred from schema.",
         "priority": "high",
         "dependencies": [
-          1
+          "1"
         ],
-        "status": "pending",
-        "subtasks": []
+        "status": "in-progress",
+        "subtasks": [],
+        "updatedAt": "2026-02-23T09:01:41.548Z"
       },
       {
-        "id": 3,
+        "id": "3",
         "title": "Implement Core Database Schema - Categories and Groups Tables",
         "description": "Create the Drizzle schema definitions for the `categories` and `groups` tables that form the base catalog structure sourced from TCGCSV.",
         "details": "Create `packages/shared/src/schema/catalog.ts` with Drizzle table definitions. Define `categories` table: `id` (integer, primary key - TCGPlayer category ID), `name` (varchar 255, not null). Define `groups` table: `id` (integer, primary key - TCGPlayer group ID), `categoryId` (integer, references categories.id), `name` (varchar 255, not null), `publishedOn` (date). Add proper TypeScript types using `InferModel` for select and insert operations. Create index on `groups.categoryId` for efficient category-based queries. Export relations using `relations()` helper for type-safe joins. Generate initial migration with `drizzle-kit generate:pg`.\n\n```typescript\nexport const categories = pgTable('categories', {\n  id: integer('id').primaryKey(),\n  name: varchar('name', { length: 255 }).notNull(),\n});\n\nexport const groups = pgTable('groups', {\n  id: integer('id').primaryKey(),\n  categoryId: integer('category_id').references(() => categories.id),\n  name: varchar('name', { length: 255 }).notNull(),\n  publishedOn: date('published_on'),\n});\n```",
         "testStrategy": "Run migration against test database. Insert sample category (id: 1, name: 'Magic: The Gathering') and group (id: 3170, name: 'Silver Tempest'). Verify foreign key constraint by attempting to insert group with invalid categoryId. Query with joins and verify TypeScript types.",
         "priority": "high",
         "dependencies": [
-          2
+          "2"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 4,
+        "id": "4",
         "title": "Implement Database Schema - Products Table",
         "description": "Create the Drizzle schema for the `products` table which stores the core product catalog with TCGPlayer IDs and cross-platform identifiers.",
         "details": "Extend `packages/shared/src/schema/catalog.ts` with products table. Define `products` table with columns: `id` (integer, primary key - TCGPlayer Product ID), `groupId` (integer, references groups.id), `name` (varchar 255, not null), `cleanName` (varchar 255), `imageUrl` (text), `scryfallId` (uuid), `mtgjsonUuid` (uuid), `tcgplayerUrl` (text), `manapoolUrl` (text), `cardNumber` (varchar 50), `rarity` (varchar 50). Create indexes on `groupId`, `scryfallId`, and `mtgjsonUuid` for efficient lookups. Add composite index on `(groupId, cardNumber)` for card lookups within sets.\n\n```typescript\nexport const products = pgTable('products', {\n  id: integer('id').primaryKey(),\n  groupId: integer('group_id').references(() => groups.id),\n  name: varchar('name', { length: 255 }).notNull(),\n  cleanName: varchar('clean_name', { length: 255 }),\n  imageUrl: text('image_url'),\n  scryfallId: uuid('scryfall_id'),\n  mtgjsonUuid: uuid('mtgjson_uuid'),\n  tcgplayerUrl: text('tcgplayer_url'),\n  manapoolUrl: text('manapool_url'),\n  cardNumber: varchar('card_number', { length: 50 }),\n  rarity: varchar('rarity', { length: 50 }),\n}, (table) => ({\n  groupIdIdx: index('products_group_id_idx').on(table.groupId),\n  scryfallIdx: index('products_scryfall_id_idx').on(table.scryfallId),\n}));\n```",
         "testStrategy": "Run migration. Insert test products with various optional fields (some with mtgjsonUuid, some without). Query products by groupId and verify join with groups table. Test upsert logic with ON CONFLICT DO UPDATE.",
         "priority": "high",
         "dependencies": [
-          3
+          "3"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 5,
+        "id": "5",
         "title": "Implement Database Schema - SKUs Table",
         "description": "Create the Drizzle schema for the `skus` table which maps product conditions, variants, and languages to specific TCGPlayer SKU IDs with pricing data.",
         "details": "Add `skus` table to catalog schema. Define columns: `id` (integer, primary key - TCGPlayer SKU ID), `productId` (integer, references products.id, not null), `condition` (varchar 10, not null - values: NM, LP, MP, HP, DMG), `variant` (varchar 10, not null - values: N for Normal, F for Foil), `language` (varchar 10, not null - default 'EN'), `marketPriceCents` (integer), `lowestListingCents` (integer). Create composite unique index on `(productId, condition, variant, language)` for efficient SKU lookups and to prevent duplicates. Create index on `productId` for efficient product-to-SKU queries. Add check constraints for valid condition and variant values using Drizzle's check helper.\n\n```typescript\nexport const skus = pgTable('skus', {\n  id: integer('id').primaryKey(),\n  productId: integer('product_id').references(() => products.id).notNull(),\n  condition: varchar('condition', { length: 10 }).notNull(),\n  variant: varchar('variant', { length: 10 }).notNull(),\n  language: varchar('language', { length: 10 }).notNull().default('EN'),\n  marketPriceCents: integer('market_price_cents'),\n  lowestListingCents: integer('lowest_listing_cents'),\n}, (table) => ({\n  productConditionIdx: uniqueIndex('skus_product_condition_variant_lang_idx')\n    .on(table.productId, table.condition, table.variant, table.language),\n}));\n```",
         "testStrategy": "Insert SKUs for a test product with different conditions (NM, LP) and variants (Normal, Foil). Verify unique constraint prevents duplicate SKUs. Test querying all SKUs for a product with joins to products table.",
         "priority": "high",
         "dependencies": [
-          4
+          "4"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 6,
+        "id": "6",
         "title": "Implement Database Schema - Physical Locations (Chaos Sort)",
         "description": "Create the Drizzle schema for the `physical_locations` table implementing the 'Chaos Sort' coordinate system for warehouse inventory organization.",
         "details": "Create `packages/shared/src/schema/inventory.ts` with physical_locations table. Define columns: `id` (uuid, primary key, default gen_random_uuid()), `coordString` (varchar 50, unique, not null - format: 'XX:XX:XX:XX:XX:XX'), `section` (varchar 10), `aisle` (varchar 10), `rack` (varchar 10), `shelf` (varchar 10), `box` (varchar 10), `slot` (varchar 10). Create utility function `parseCoordString(coord: string)` that extracts individual components. Add database trigger or application-level validation to ensure `coordString` format matches pattern and individual columns stay in sync. Create GIN index on `coordString` for fast lookups.\n\n```typescript\nexport const physicalLocations = pgTable('physical_locations', {\n  id: uuid('id').primaryKey().defaultRandom(),\n  coordString: varchar('coord_string', { length: 50 }).unique().notNull(),\n  section: varchar('section', { length: 10 }),\n  aisle: varchar('aisle', { length: 10 }),\n  rack: varchar('rack', { length: 10 }),\n  shelf: varchar('shelf', { length: 10 }),\n  box: varchar('box', { length: 10 }),\n  slot: varchar('slot', { length: 10 }),\n});\n\n// Utility function\nexport function parseCoordString(coord: string) {\n  const [section, aisle, rack, shelf, box, slot] = coord.split(':');\n  return { section, aisle, rack, shelf, box, slot };\n}\n```",
         "testStrategy": "Insert physical locations with various coord_string values. Verify unique constraint on coord_string. Test parseCoordString utility function with valid and invalid inputs. Query locations by section prefix.",
         "priority": "high",
         "dependencies": [
-          2
+          "2"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 7,
+        "id": "7",
         "title": "Implement Database Schema - Inventory Table",
         "description": "Create the Drizzle schema for the `inventory` table that links SKUs to physical locations with quantity and pricing information.",
         "details": "Extend `packages/shared/src/schema/inventory.ts` with inventory table. Define columns: `id` (uuid, primary key), `skuId` (integer, references skus.id, not null), `locationId` (uuid, references physical_locations.id), `quantity` (integer, default 0), `listedPriceCents` (integer, not null), `lastSyncedTcgplayer` (timestamp), `lastSyncedManapool` (timestamp). Create composite index on `(skuId, locationId)` for inventory lookups. Create index on `locationId` for location-based queries during order picking. Add quantity check constraint to prevent negative values.\n\n```typescript\nexport const inventory = pgTable('inventory', {\n  id: uuid('id').primaryKey().defaultRandom(),\n  skuId: integer('sku_id').references(() => skus.id).notNull(),\n  locationId: uuid('location_id').references(() => physicalLocations.id),\n  quantity: integer('quantity').default(0),\n  listedPriceCents: integer('listed_price_cents').notNull(),\n  lastSyncedTcgplayer: timestamp('last_synced_tcgplayer'),\n  lastSyncedManapool: timestamp('last_synced_manapool'),\n});\n```",
         "testStrategy": "Insert inventory records linking to test SKUs and locations. Test quantity updates (increment/decrement). Verify foreign key constraints. Query inventory with full joins to SKUs, products, and locations for pull sheet generation.",
         "priority": "high",
         "dependencies": [
-          5,
-          6
+          "5",
+          "6"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 8,
+        "id": "8",
         "title": "Implement Database Schema - Orders and Order Lines Tables",
         "description": "Create the Drizzle schema for the unified `orders` and `order_lines` tables that handle multi-marketplace order orchestration.",
         "details": "Create `packages/shared/src/schema/orders.ts`. Define `orders` table: `id` (uuid, primary key), `marketplace` (varchar 50, not null - enum: TCGPLAYER, MANAPOOL, INTERNAL), `marketplaceOrderId` (varchar 255, unique, not null), `status` (varchar 50, default 'PENDING' - enum: PENDING, PRINTED, SHIPPED, CANCELLED), `buyerName` (varchar 255), `shippingAddress` (jsonb, not null), `totalCents` (integer, not null), `shippingCents` (integer, not null), `trackingNumber` (varchar 255), `easypostLabelUrl` (text), `createdAt` (timestamp, default now()). Define `order_lines` table: `id` (uuid, primary key), `orderId` (uuid, references orders.id), `inventoryId` (uuid, references inventory.id), `quantity` (integer, not null), `priceCents` (integer, not null). Create indexes on orders.status and orders.marketplace for filtering.\n\n```typescript\nexport const marketplaceEnum = pgEnum('marketplace', ['TCGPLAYER', 'MANAPOOL', 'INTERNAL']);\nexport const orderStatusEnum = pgEnum('order_status', ['PENDING', 'PRINTED', 'SHIPPED', 'CANCELLED']);\n\nexport const orders = pgTable('orders', {\n  id: uuid('id').primaryKey().defaultRandom(),\n  marketplace: marketplaceEnum('marketplace').notNull(),\n  marketplaceOrderId: varchar('marketplace_order_id', { length: 255 }).unique().notNull(),\n  status: orderStatusEnum('status').default('PENDING'),\n  buyerName: varchar('buyer_name', { length: 255 }),\n  shippingAddress: jsonb('shipping_address').notNull(),\n  totalCents: integer('total_cents').notNull(),\n  shippingCents: integer('shipping_cents').notNull(),\n  trackingNumber: varchar('tracking_number', { length: 255 }),\n  easypostLabelUrl: text('easypost_label_url'),\n  createdAt: timestamp('created_at').defaultNow(),\n});\n```",
         "testStrategy": "Insert orders from different marketplaces. Test status transitions. Verify JSONB shipping address querying. Test order with multiple order_lines. Query orders with full resolution to inventory and products for pull sheet display.",
         "priority": "high",
         "dependencies": [
-          7
+          "7"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 9,
+        "id": "9",
         "title": "Implement Database Schema - Print Jobs Table",
         "description": "Create the Drizzle schema for the `print_jobs` table that manages the serverless print queue consumed by the warehouse hardware daemon.",
         "details": "Add `print_jobs` table to `packages/shared/src/schema/system.ts`. Define columns: `id` (uuid, primary key), `jobType` (varchar 20, not null - enum: LABEL, PULL_SHEET, PACKING_SLIP), `status` (varchar 20, default 'PENDING' - enum: PENDING, PROCESSING, COMPLETED, FAILED), `payload` (jsonb, not null - contains URL for PDF or ZPL string for labels), `orderId` (uuid, references orders.id, nullable), `printerTarget` (varchar 50 - e.g., 'BROTHER_QL1100', 'LASER_MAIN'), `createdAt` (timestamp, default now()), `processedAt` (timestamp), `errorMessage` (text). Create index on `(status, createdAt)` for efficient queue polling by the daemon.\n\n```typescript\nexport const printJobTypeEnum = pgEnum('print_job_type', ['LABEL', 'PULL_SHEET', 'PACKING_SLIP']);\nexport const printJobStatusEnum = pgEnum('print_job_status', ['PENDING', 'PROCESSING', 'COMPLETED', 'FAILED']);\n\nexport const printJobs = pgTable('print_jobs', {\n  id: uuid('id').primaryKey().defaultRandom(),\n  jobType: printJobTypeEnum('job_type').notNull(),\n  status: printJobStatusEnum('status').default('PENDING'),\n  payload: jsonb('payload').notNull(),\n  orderId: uuid('order_id').references(() => orders.id),\n  printerTarget: varchar('printer_target', { length: 50 }),\n  createdAt: timestamp('created_at').defaultNow(),\n  processedAt: timestamp('processed_at'),\n  errorMessage: text('error_message'),\n}, (table) => ({\n  statusCreatedIdx: index('print_jobs_status_created_idx').on(table.status, table.createdAt),\n}));\n```",
         "testStrategy": "Insert print jobs with different types. Test status transitions from PENDING to PROCESSING to COMPLETED. Test FAILED status with error message. Query pending jobs ordered by createdAt (FIFO).",
         "priority": "medium",
         "dependencies": [
-          8
+          "8"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 10,
+        "id": "10",
         "title": "Implement Database Schema - Shipping Rules Table",
         "description": "Create the Drizzle schema for the `shipping_rules` table that stores configurable thresholds for automated shipping method selection.",
         "details": "Create `packages/shared/src/schema/settings.ts` with shipping_rules table. Define columns: `id` (uuid, primary key), `ruleName` (varchar 100, unique, not null - e.g., 'usps_letter_thresholds', 'usps_ground_advantage_thresholds'), `config` (jsonb, not null - stores threshold values), `isActive` (boolean, default true), `createdAt` (timestamp), `updatedAt` (timestamp). Create TypeScript interfaces for the expected config shapes.\n\n```typescript\nexport const shippingRules = pgTable('shipping_rules', {\n  id: uuid('id').primaryKey().defaultRandom(),\n  ruleName: varchar('rule_name', { length: 100 }).unique().notNull(),\n  config: jsonb('config').notNull(),\n  isActive: boolean('is_active').default(true),\n  createdAt: timestamp('created_at').defaultNow(),\n  updatedAt: timestamp('updated_at'),\n});\n\n// Type definitions for config shapes\nexport interface UspsLetterConfig {\n  maxCardCount: number;\n  maxOrderValueCents: number;\n  defaultWeightOz: number;\n}\n\nexport interface UspsGroundAdvantageConfig {\n  defaultWeightOz: number;\n}\n```\n\nSeed initial rules with default thresholds from PRD.",
         "testStrategy": "Insert default shipping rules. Query active rules. Test config JSON structure validation. Update threshold values and verify updatedAt timestamp changes.",
         "priority": "medium",
         "dependencies": [
-          2
+          "2"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 11,
+        "id": "11",
         "title": "Run Database Migrations and Generate TypeScript Types",
         "description": "Execute all Drizzle migrations against the database and set up automatic TypeScript type generation from the schema.",
         "details": "Create migration scripts in `packages/shared/drizzle/`. Configure `drizzle.config.ts` with proper output paths. Run `npx drizzle-kit generate:pg` to create migration files. Execute `npx drizzle-kit push:pg` or create a migration runner script `packages/shared/src/db/migrate.ts` using `drizzle-orm/node-postgres/migrator`. Set up npm scripts: `db:generate` for generating migrations, `db:push` for direct schema push in dev, `db:migrate` for running migrations in production. Export all table types and relations from `packages/shared/src/schema/index.ts`. Configure path aliases so `@shared/schema` works across packages.\n\n```typescript\n// packages/shared/src/db/migrate.ts\nimport { drizzle } from 'drizzle-orm/node-postgres';\nimport { migrate } from 'drizzle-orm/node-postgres/migrator';\nimport { Pool } from 'pg';\n\nconst pool = new Pool({ connectionString: process.env.DATABASE_URL });\nconst db = drizzle(pool);\n\nawait migrate(db, { migrationsFolder: './drizzle' });\nawait pool.end();\n```",
         "testStrategy": "Run migrations on fresh database. Verify all tables created with correct columns and constraints. Test rollback capability. Confirm TypeScript types compile correctly. Run a complex query joining all tables to verify relations.",
         "priority": "high",
         "dependencies": [
-          3,
-          4,
-          5,
-          6,
-          7,
-          8,
-          9,
-          10
+          "3",
+          "4",
+          "5",
+          "6",
+          "7",
+          "8",
+          "9",
+          "10"
         ],
         "status": "pending",
         "subtasks": [
@@ -221,7 +229,8 @@
             "dependencies": [],
             "details": "Create drizzle.config.ts at the project root with the following configuration: Set dialect to 'postgresql', schema path to './packages/shared/src/schema/*.ts' to include all schema files (catalog.ts, inventory.ts, orders.ts, system.ts, settings.ts), out directory to './packages/shared/drizzle' for generated migrations, and dbCredentials reading connectionString from process.env.DATABASE_URL. Install drizzle-kit as a dev dependency in the root package.json if not already present. Ensure the config exports using defineConfig from drizzle-kit.",
             "status": "pending",
-            "testStrategy": "Run `npx drizzle-kit check` to verify the configuration is valid and schema files are found. Verify the config correctly references all schema files by checking the drizzle-kit output shows all tables from catalog.ts, inventory.ts, orders.ts, system.ts, and settings.ts."
+            "testStrategy": "Run `npx drizzle-kit check` to verify the configuration is valid and schema files are found. Verify the config correctly references all schema files by checking the drizzle-kit output shows all tables from catalog.ts, inventory.ts, orders.ts, system.ts, and settings.ts.",
+            "parentId": "undefined"
           },
           {
             "id": 2,
@@ -232,7 +241,8 @@
             ],
             "details": "Create packages/shared/src/db/migrate.ts with the following implementation: Import drizzle from 'drizzle-orm/node-postgres', migrate from 'drizzle-orm/node-postgres/migrator', and Pool from 'pg'. Create a Pool instance using DATABASE_URL from environment. Initialize drizzle with the pool. Call migrate() with migrationsFolder pointing to './drizzle' (relative to packages/shared). Ensure pool.end() is called after migration completes. Handle errors gracefully with proper exit codes. Add a shebang for direct execution with ts-node or tsx.",
             "status": "pending",
-            "testStrategy": "Run the migration script against a fresh local PostgreSQL database. Verify all tables are created with correct columns, constraints, and indexes by querying information_schema. Check that running the script a second time completes without errors (idempotent)."
+            "testStrategy": "Run the migration script against a fresh local PostgreSQL database. Verify all tables are created with correct columns, constraints, and indexes by querying information_schema. Check that running the script a second time completes without errors (idempotent).",
+            "parentId": "undefined"
           },
           {
             "id": 3,
@@ -244,7 +254,8 @@
             ],
             "details": "Add the following scripts to the root package.json: 'db:generate': 'drizzle-kit generate' - generates SQL migration files from schema changes, 'db:push': 'drizzle-kit push' - pushes schema directly to database for rapid development iteration (bypasses migrations), 'db:migrate': 'tsx packages/shared/src/db/migrate.ts' - runs the production migration script. Install tsx as a dev dependency for running TypeScript directly. Optionally add 'db:studio': 'drizzle-kit studio' for visual database inspection. Ensure scripts are workspace-aware if using npm workspaces.",
             "status": "pending",
-            "testStrategy": "Run 'npm run db:generate' and verify migration SQL files are created in packages/shared/drizzle/. Run 'npm run db:push' against a test database and verify tables are created. Run 'npm run db:migrate' and verify it executes the migration runner successfully."
+            "testStrategy": "Run 'npm run db:generate' and verify migration SQL files are created in packages/shared/drizzle/. Run 'npm run db:push' against a test database and verify tables are created. Run 'npm run db:migrate' and verify it executes the migration runner successfully.",
+            "parentId": "undefined"
           },
           {
             "id": 4,
@@ -253,154 +264,156 @@
             "dependencies": [],
             "details": "Create packages/shared/src/schema/index.ts that re-exports all tables, types, and relations from catalog.ts (categories, groups, products, skus), inventory.ts (physicalLocations, inventory), orders.ts (orders, orderLines, marketplaceEnum, orderStatusEnum), system.ts (printJobs, printJobTypeEnum, printJobStatusEnum), and settings.ts (shippingRules). Use 'export * from' syntax for each schema file. Export inferred types using InferSelectModel and InferInsertModel for each table. Configure tsconfig.base.json paths to map '@shared/*' to 'packages/shared/src/*'. Update each workspace tsconfig.json to extend base config and include the path mapping. If using TypeScript project references, ensure proper references are configured.",
             "status": "pending",
-            "testStrategy": "Import types from '@shared/schema' in packages/web and packages/worker. Run 'tsc --noEmit' in each workspace to verify TypeScript resolves the path alias correctly. Test IDE autocomplete for imported table types and ensure InferSelectModel types are accessible."
+            "testStrategy": "Import types from '@shared/schema' in packages/web and packages/worker. Run 'tsc --noEmit' in each workspace to verify TypeScript resolves the path alias correctly. Test IDE autocomplete for imported table types and ensure InferSelectModel types are accessible.",
+            "parentId": "undefined"
           }
         ]
       },
       {
-        "id": 12,
+        "id": "12",
         "title": "Set Up Redis and BullMQ for Worker Package",
         "description": "Configure Redis connection and BullMQ queue infrastructure for the persistent ETL worker that handles long-running tasks.",
         "details": "Install dependencies in `packages/worker`: `bullmq`, `ioredis`. Create `packages/worker/src/config/redis.ts` with Redis connection using `REDIS_URL` environment variable. Create base queue configuration in `packages/worker/src/queues/index.ts`. Define queues: `catalog-sync` (for TCGCSV/TCGTracking ETL), `tcgplayer-session` (for Puppeteer auth), `tcgplayer-orders` (for order syncing), `scraper-fallback` (for SKU scraping). Configure queue options: job retention, stalled job handling, rate limiting. Set up BullMQ dashboard for monitoring (optional: bull-board or arena).\n\n```typescript\n// packages/worker/src/queues/index.ts\nimport { Queue, Worker, QueueScheduler } from 'bullmq';\nimport { redisConnection } from '../config/redis';\n\nexport const catalogSyncQueue = new Queue('catalog-sync', {\n  connection: redisConnection,\n  defaultJobOptions: {\n    attempts: 3,\n    backoff: { type: 'exponential', delay: 5000 },\n    removeOnComplete: { age: 86400 },\n    removeOnFail: { age: 604800 },\n  },\n});\n\n// Start scheduler for delayed/repeated jobs\nnew QueueScheduler('catalog-sync', { connection: redisConnection });\n```",
         "testStrategy": "Connect to Redis successfully. Add test job to queue. Verify job appears in queue. Process test job with simple worker. Test job retry on failure.",
         "priority": "high",
         "dependencies": [
-          1
+          "1"
         ],
-        "status": "pending",
-        "subtasks": []
+        "status": "in-progress",
+        "subtasks": [],
+        "updatedAt": "2026-02-23T09:01:43.704Z"
       },
       {
-        "id": 13,
+        "id": "13",
         "title": "Implement TCGCSV Catalog ETL - Categories and Groups Sync",
         "description": "Create the BullMQ worker that fetches categories and groups from TCGCSV API and upserts them into the database.",
         "details": "Create `packages/worker/src/jobs/tcgcsv-sync.ts`. Read `SUPPORTED_CATEGORIES` from environment (comma-separated: '1,3,27'). For each category ID, fetch from `https://tcgcsv.com/tcgplayer/{categoryId}/categories.json` and `https://tcgcsv.com/tcgplayer/{categoryId}/groups.json`. Parse JSON responses. Use Drizzle's `onConflictDoUpdate` for upsert logic. Process categories first, then groups (due to foreign key). Use streaming/pagination if response is large. Log progress and handle rate limiting with exponential backoff.\n\n```typescript\n// Pseudo-code for worker\nworker.on('catalog-sync:categories', async (job) => {\n  const categories = process.env.SUPPORTED_CATEGORIES.split(',').map(Number);\n  \n  for (const catId of categories) {\n    const response = await fetch(`https://tcgcsv.com/tcgplayer/${catId}/categories.json`);\n    const data = await response.json();\n    \n    await db.insert(categoriesTable)\n      .values(data.categories)\n      .onConflictDoUpdate({\n        target: categoriesTable.id,\n        set: { name: sql`excluded.name` }\n      });\n    \n    // Then sync groups for this category\n    const groupsResponse = await fetch(`https://tcgcsv.com/tcgplayer/${catId}/groups.json`);\n    // ... upsert groups\n  }\n});\n```",
         "testStrategy": "Run sync job for a single category (MTG = 1). Verify categories and groups inserted. Run again and verify upsert updates existing records without duplicates. Test with invalid category ID to verify error handling.",
         "priority": "high",
         "dependencies": [
-          11,
-          12
+          "11",
+          "12"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 14,
+        "id": "14",
         "title": "Implement TCGCSV Catalog ETL - Products Sync",
         "description": "Extend the ETL worker to sync products from TCGCSV for all configured categories, handling large datasets efficiently.",
         "details": "Extend `packages/worker/src/jobs/tcgcsv-sync.ts`. For each group fetched, fetch products from `https://tcgcsv.com/tcgplayer/{categoryId}/groups/{groupId}/products.json`. Implement batch processing to handle large product lists (chunk into batches of 500). Use database transactions for atomic batch inserts. Map TCGCSV fields to our schema (handling null/missing values). Track sync progress in Redis for resumability. Implement parallel group processing with concurrency limit (e.g., 3 concurrent groups).\n\n```typescript\nasync function syncProducts(categoryId: number, groupId: number) {\n  const response = await fetch(\n    `https://tcgcsv.com/tcgplayer/${categoryId}/groups/${groupId}/products.json`\n  );\n  const { products } = await response.json();\n  \n  // Batch upsert\n  const chunks = chunk(products, 500);\n  for (const batch of chunks) {\n    await db.insert(productsTable)\n      .values(batch.map(p => ({\n        id: p.productId,\n        groupId: groupId,\n        name: p.name,\n        cleanName: p.cleanName,\n        imageUrl: p.imageUrl,\n        cardNumber: p.extendedData?.find(e => e.name === 'Number')?.value,\n        rarity: p.extendedData?.find(e => e.name === 'Rarity')?.value,\n      })))\n      .onConflictDoUpdate({ target: productsTable.id, set: { ... } });\n  }\n}\n```",
         "testStrategy": "Run products sync for a small group. Verify products inserted with correct foreign keys. Run for larger group and verify batching works. Test resumability after simulated failure.",
         "priority": "high",
         "dependencies": [
-          13
+          "13"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 15,
+        "id": "15",
         "title": "Implement TCGTracking API SKU Enrichment Worker",
         "description": "Create the BullMQ worker that fetches SKU data from TCGTracking API to enrich products with condition/variant information and MTGJSON UUIDs.",
         "details": "Create `packages/worker/src/jobs/tcgtracking-sync.ts`. After TCGCSV sync completes, queue TCGTracking enrichment jobs. For each supported category, fetch from TCGTracking's Open TCG API `/skus` endpoint. Map response to our `skus` table schema. Extract `mtgjsonUuid` and update parent product record. Handle cases where TCGTracking doesn't have data (Pokemon, Lorcana) gracefully - these will rely on fallback scrapers. Implement cron scheduling based on `CRON_TCGTRACKING_SYNC` environment variable.\n\n```typescript\n// Worker for TCGTracking enrichment\nworker.on('catalog-sync:tcgtracking', async (job) => {\n  const { productIds } = job.data; // Batch of product IDs to enrich\n  \n  for (const productId of productIds) {\n    const response = await fetch(\n      `https://api.tcgtracking.com/v1/products/${productId}/skus`\n    );\n    \n    if (response.status === 404) continue; // TCGTracking doesn't have this product\n    \n    const { skus, mtgjsonUuid } = await response.json();\n    \n    // Update product with mtgjsonUuid if available\n    if (mtgjsonUuid) {\n      await db.update(productsTable)\n        .set({ mtgjsonUuid })\n        .where(eq(productsTable.id, productId));\n    }\n    \n    // Insert SKUs\n    await db.insert(skusTable)\n      .values(skus.map(s => ({ ... })))\n      .onConflictDoUpdate(...);\n  }\n});\n```",
         "testStrategy": "Run enrichment for known MTG product. Verify SKUs created with correct conditions/variants. Verify mtgjsonUuid populated on product. Test with non-MTG product and verify graceful skip.",
         "priority": "high",
         "dependencies": [
-          14
+          "14"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 16,
+        "id": "16",
         "title": "Implement Puppeteer Stealth Session Manager for TCGPlayer",
         "description": "Create the Puppeteer-based authentication flow that intercepts TCGPlayer admin session tokens and stores them in Redis for API access.",
         "details": "Install `puppeteer-extra`, `puppeteer-extra-plugin-stealth`, and `puppeteer` in `packages/worker`. Create `packages/worker/src/services/tcgplayer-auth.ts`. Launch Puppeteer with stealth plugin to bypass DataDome. Navigate to `store.tcgplayer.com/admin/login`. Fill credentials from `TCGPLAYER_USERNAME` and `TCGPLAYER_PASSWORD` environment variables. Set up request interception to capture Authorization header from XHR requests to `order-management-api.tcgplayer.com`. Store captured token in Redis with TTL (e.g., 1 hour). Close browser after token capture to free resources.\n\n```typescript\nimport puppeteer from 'puppeteer-extra';\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth';\n\npuppeteer.use(StealthPlugin());\n\nexport async function captureTcgplayerToken(): Promise<string> {\n  const browser = await puppeteer.launch({ headless: true });\n  const page = await browser.newPage();\n  \n  let capturedToken: string | null = null;\n  \n  await page.setRequestInterception(true);\n  page.on('request', (request) => {\n    const headers = request.headers();\n    if (request.url().includes('order-management-api') && headers.authorization) {\n      capturedToken = headers.authorization.replace('Bearer ', '');\n    }\n    request.continue();\n  });\n  \n  await page.goto('https://store.tcgplayer.com/admin/login');\n  await page.type('#Username', process.env.TCGPLAYER_USERNAME!);\n  await page.type('#Password', process.env.TCGPLAYER_PASSWORD!);\n  await page.click('button[type=\"submit\"]');\n  await page.waitForNavigation();\n  \n  // Navigate to orders page to trigger API call\n  await page.goto('https://store.tcgplayer.com/admin/orders');\n  await page.waitForTimeout(3000);\n  \n  await browser.close();\n  \n  if (!capturedToken) throw new Error('Failed to capture token');\n  \n  await redis.set('tcgplayer:auth:token', capturedToken, 'EX', 3600);\n  return capturedToken;\n}\n```",
         "testStrategy": "Create standalone test script `scripts/test-tcg-auth.ts` with headless: false to observe. Verify token captured and logged. Use captured token to curl /orders endpoint manually. Verify Redis TTL is set correctly.",
         "priority": "high",
         "dependencies": [
-          12
+          "12"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 17,
+        "id": "17",
         "title": "Implement TCGPlayer Order Sync Worker",
         "description": "Create the worker that uses the captured TCGPlayer session to fetch orders from the undocumented API and normalize them into the unified orders table.",
         "details": "Create `packages/worker/src/jobs/tcgplayer-orders.ts`. Implement token retrieval from Redis with automatic refresh via Puppeteer when expired/invalid. Make requests to `POST https://order-management-api.tcgplayer.com/orders/search` with status filter for new orders. For each order, fetch details from `GET https://order-management-api.tcgplayer.com/orders/{orderId}`. Map TCGPlayer response to our unified orders schema. Create order_lines linking to existing SKUs in our database. Handle 401 responses by re-triggering Puppeteer auth flow.\n\n```typescript\nworker.on('tcgplayer-orders:sync', async (job) => {\n  const token = await getValidToken(); // Checks Redis, refreshes if needed\n  \n  const searchResponse = await fetch(\n    'https://order-management-api.tcgplayer.com/orders/search',\n    {\n      method: 'POST',\n      headers: { Authorization: `Bearer ${token}` },\n      body: JSON.stringify({ status: 'New', page: 1, pageSize: 50 }),\n    }\n  );\n  \n  if (searchResponse.status === 401) {\n    await catalogSyncQueue.add('tcgplayer:refresh-token', {});\n    throw new Error('Token expired, retrying...');\n  }\n  \n  const { orders } = await searchResponse.json();\n  \n  for (const order of orders) {\n    const details = await fetchOrderDetails(token, order.orderNumber);\n    \n    await db.transaction(async (tx) => {\n      const [insertedOrder] = await tx.insert(ordersTable).values({\n        marketplace: 'TCGPLAYER',\n        marketplaceOrderId: order.orderNumber,\n        buyerName: details.shippingAddress.recipientName,\n        shippingAddress: details.shippingAddress,\n        totalCents: details.total * 100,\n        shippingCents: details.shipping * 100,\n      }).returning();\n      \n      // Insert order lines...\n    });\n  }\n});\n```",
         "testStrategy": "Run with valid token and verify orders imported. Test 401 handling triggers re-auth. Verify order_lines correctly link to SKUs. Test duplicate order handling (should skip or update).",
         "priority": "high",
         "dependencies": [
-          16,
-          8
+          "16",
+          "8"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 18,
+        "id": "18",
         "title": "Create ManaPool Webhook Endpoint for Order Ingestion",
         "description": "Build the Next.js API route that receives ManaPool order webhooks, validates signatures, and normalizes orders into the unified database.",
         "details": "Create `packages/web/src/app/api/webhooks/manapool/order-created/route.ts`. Implement webhook signature validation using `MANAPOOL_WEBHOOK_SECRET`. Parse incoming order payload per PRD structure. Map ManaPool items to our SKUs by matching `product_id` + `condition` + `foil`. Handle missing SKUs gracefully (log warning, still create order). Use database transaction for atomic order + order_lines insert.\n\n```typescript\nimport { NextRequest, NextResponse } from 'next/server';\nimport crypto from 'crypto';\n\nexport async function POST(request: NextRequest) {\n  const signature = request.headers.get('x-manapool-signature');\n  const body = await request.text();\n  \n  // Validate webhook signature\n  const expectedSignature = crypto\n    .createHmac('sha256', process.env.MANAPOOL_WEBHOOK_SECRET!)\n    .update(body)\n    .digest('hex');\n  \n  if (signature !== expectedSignature) {\n    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });\n  }\n  \n  const payload = JSON.parse(body);\n  \n  if (payload.event !== 'order.created') {\n    return NextResponse.json({ status: 'ignored' });\n  }\n  \n  const { data } = payload;\n  \n  await db.transaction(async (tx) => {\n    // Insert order\n    const [order] = await tx.insert(ordersTable).values({\n      marketplace: 'MANAPOOL',\n      marketplaceOrderId: data.id,\n      buyerName: data.buyer.name,\n      shippingAddress: data.shipping,\n      totalCents: data.totals.grand_total,\n      shippingCents: data.totals.shipping,\n    }).returning();\n    \n    // Map items to SKUs and insert order_lines\n    for (const item of data.items) {\n      const sku = await findSkuByProductConditionVariant(\n        tx, item.product_id, item.condition, item.foil ? 'F' : 'N'\n      );\n      // ...\n    }\n  });\n  \n  return NextResponse.json({ status: 'received' });\n}\n```",
         "testStrategy": "Send test webhook with valid signature, verify order created. Send with invalid signature, verify 401 response. Test with item that has no matching SKU. Verify idempotency (same order ID doesn't duplicate).",
         "priority": "high",
         "dependencies": [
-          8,
-          1
+          "8",
+          "1"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 19,
+        "id": "19",
         "title": "Implement EasyPost Shipping Integration",
         "description": "Create the shipping service that integrates with EasyPost API for address verification, rate shopping, and label generation based on configurable rules.",
         "details": "Install `@easypost/api` in `packages/web`. Create `packages/web/src/services/shipping.ts`. Implement address verification with `client.Address.create({ verify: true })`. Create shipping rules engine that reads from `shipping_rules` table. Evaluate order against thresholds (card count, order value) to determine USPS Letter vs Ground Advantage. Create shipment with appropriate parcel dimensions. Select rate by service type. Purchase label and store tracking number + label URL.\n\n```typescript\nimport EasyPost from '@easypost/api';\n\nconst client = new EasyPost(process.env.EASYPOST_API_KEY!);\n\nexport async function createShipment(orderId: string) {\n  const order = await getOrderWithLines(orderId);\n  const rules = await getActiveShippingRules();\n  \n  // Verify address\n  const toAddress = await client.Address.create({\n    verify: ['delivery'],\n    ...mapToEasyPostAddress(order.shippingAddress),\n  });\n  \n  if (!toAddress.verifications?.delivery?.success) {\n    throw new ShippingError('Address verification failed', toAddress.verifications);\n  }\n  \n  // Evaluate shipping method\n  const cardCount = order.orderLines.reduce((sum, l) => sum + l.quantity, 0);\n  const letterRules = rules.find(r => r.ruleName === 'usps_letter_thresholds')?.config;\n  \n  const useLetterMail = \n    cardCount <= letterRules.maxCardCount &&\n    order.totalCents <= letterRules.maxOrderValueCents;\n  \n  const parcel = useLetterMail\n    ? { length: 9.5, width: 4.1, height: 0.1, weight: letterRules.defaultWeightOz }\n    : { length: 10, width: 8, height: 2, weight: 4.0 };\n  \n  const shipment = await client.Shipment.create({\n    to_address: toAddress,\n    from_address: await getWarehouseAddress(),\n    parcel,\n  });\n  \n  const targetService = useLetterMail ? 'First' : 'GroundAdvantage';\n  const rate = shipment.rates.find(r => r.service === targetService);\n  \n  const purchased = await client.Shipment.buy(shipment.id, rate.id);\n  \n  // Update order and create print job\n  await db.update(ordersTable)\n    .set({\n      trackingNumber: purchased.tracking_code,\n      easypostLabelUrl: purchased.postage_label.label_url,\n    })\n    .where(eq(ordersTable.id, orderId));\n  \n  await createPrintJob('LABEL', { url: purchased.postage_label.label_url }, orderId);\n  \n  return purchased;\n}\n```",
         "testStrategy": "Test address verification with valid and invalid addresses. Test threshold evaluation with various card counts. Mock EasyPost API for unit tests. Integration test with EasyPost test keys for actual label generation.",
         "priority": "high",
         "dependencies": [
-          10,
-          8
+          "10",
+          "8"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 20,
+        "id": "20",
         "title": "Create Print Queue API Endpoints",
         "description": "Build the Next.js API routes for the print queue that the warehouse hardware daemon will poll to fetch pending print jobs.",
         "details": "Create `packages/web/src/app/api/internal/print-queue/route.ts` for GET (fetch pending) and POST (add new job). Create `packages/web/src/app/api/internal/print-queue/[id]/complete/route.ts` for marking jobs complete. Implement API key authentication for internal endpoints. Return jobs in FIFO order. Include job type, payload, and target printer.\n\n```typescript\n// GET /api/internal/print-queue\nexport async function GET(request: NextRequest) {\n  // Validate internal API key\n  const apiKey = request.headers.get('x-api-key');\n  if (apiKey !== process.env.INTERNAL_API_KEY) {\n    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });\n  }\n  \n  // Fetch pending jobs, limit to 10, FIFO order\n  const jobs = await db.select()\n    .from(printJobsTable)\n    .where(eq(printJobsTable.status, 'PENDING'))\n    .orderBy(printJobsTable.createdAt)\n    .limit(10);\n  \n  // Mark as processing to prevent duplicate pickup\n  const jobIds = jobs.map(j => j.id);\n  if (jobIds.length > 0) {\n    await db.update(printJobsTable)\n      .set({ status: 'PROCESSING' })\n      .where(inArray(printJobsTable.id, jobIds));\n  }\n  \n  return NextResponse.json({ jobs });\n}\n\n// POST /api/internal/print-queue/[id]/complete\nexport async function POST(request: NextRequest, { params }: { params: { id: string } }) {\n  const { success, errorMessage } = await request.json();\n  \n  await db.update(printJobsTable)\n    .set({\n      status: success ? 'COMPLETED' : 'FAILED',\n      processedAt: new Date(),\n      errorMessage: success ? null : errorMessage,\n    })\n    .where(eq(printJobsTable.id, params.id));\n  \n  return NextResponse.json({ status: 'ok' });\n}\n```",
         "testStrategy": "Create print jobs via DB. Poll endpoint and verify jobs returned. Mark complete and verify status updated. Test FIFO ordering. Test unauthorized access rejection.",
         "priority": "medium",
         "dependencies": [
-          9
+          "9"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 21,
+        "id": "21",
         "title": "Build Warehouse Hardware Daemon",
         "description": "Create the Node.js daemon application that runs on warehouse PC, polls the print queue with exponential backoff, and routes jobs to local printers.",
         "details": "Create `packages/daemon/` as standalone Node.js application. Implement exponential backoff polling (5s -> 10s -> 30s -> 60s max, reset on job found). Support multiple printer types via configuration: thermal label printer (raw socket to port 9100), PDF printer (OS command). Handle network failures gracefully with retry logic. Log all operations for debugging.\n\n```typescript\n// packages/daemon/src/index.ts\nimport axios from 'axios';\nimport { exec } from 'child_process';\nimport net from 'net';\n\nconst API_URL = process.env.API_URL;\nconst API_KEY = process.env.INTERNAL_API_KEY;\n\nlet pollInterval = 5000;\nconst MAX_POLL_INTERVAL = 60000;\n\nasync function poll() {\n  try {\n    const { data } = await axios.get(`${API_URL}/api/internal/print-queue`, {\n      headers: { 'x-api-key': API_KEY },\n    });\n    \n    if (data.jobs.length > 0) {\n      pollInterval = 5000; // Reset backoff\n      \n      for (const job of data.jobs) {\n        try {\n          await processJob(job);\n          await markComplete(job.id, true);\n        } catch (error) {\n          await markComplete(job.id, false, error.message);\n        }\n      }\n    } else {\n      // Exponential backoff\n      pollInterval = Math.min(pollInterval * 2, MAX_POLL_INTERVAL);\n    }\n  } catch (error) {\n    console.error('Poll failed:', error.message);\n  }\n  \n  setTimeout(poll, pollInterval);\n}\n\nasync function processJob(job: PrintJob) {\n  if (job.jobType === 'LABEL') {\n    // Download and send to thermal printer\n    const labelData = await axios.get(job.payload.url, { responseType: 'arraybuffer' });\n    await sendToThermalPrinter(labelData.data);\n  } else if (job.jobType === 'PULL_SHEET' || job.jobType === 'PACKING_SLIP') {\n    // Download PDF and print via OS\n    const pdfPath = await downloadPdf(job.payload.url);\n    await printPdf(pdfPath, job.printerTarget);\n  }\n}\n\nasync function sendToThermalPrinter(data: Buffer) {\n  const socket = new net.Socket();\n  socket.connect(9100, process.env.THERMAL_PRINTER_IP);\n  socket.write(data);\n  socket.end();\n}\n\npoll();\n```",
         "testStrategy": "Run daemon locally. Push test label job to queue. Verify daemon picks up job and attempts print. Test backoff by checking poll intervals when queue empty. Test failure handling and error reporting.",
         "priority": "medium",
         "dependencies": [
-          20
+          "20"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 22,
+        "id": "22",
         "title": "Build Dashboard UI - Orders List View",
         "description": "Create the Next.js dashboard page displaying all orders from all marketplaces with filtering, sorting, and status management capabilities.",
         "details": "Create `packages/web/src/app/dashboard/orders/page.tsx`. Use Radix UI components for consistent styling. Display orders in table with columns: marketplace icon, order ID, buyer name, status, total, created date. Implement filters by marketplace and status. Add sort by date. Include status badge with color coding. Link to order detail view. Use React Query or SWR for data fetching with optimistic updates.\n\n```tsx\n// packages/web/src/app/dashboard/orders/page.tsx\nimport { Table, Badge, Select, Flex } from '@radix-ui/themes';\n\nexport default async function OrdersPage({ searchParams }) {\n  const orders = await getOrders({\n    marketplace: searchParams.marketplace,\n    status: searchParams.status,\n    page: searchParams.page || 1,\n  });\n  \n  return (\n    <div className=\"p-6\">\n      <Flex justify=\"between\" align=\"center\" mb=\"4\">\n        <h1 className=\"text-2xl font-bold\">Orders</h1>\n        <Flex gap=\"3\">\n          <Select.Root defaultValue={searchParams.marketplace || 'all'}>\n            <Select.Trigger placeholder=\"Marketplace\" />\n            <Select.Content>\n              <Select.Item value=\"all\">All</Select.Item>\n              <Select.Item value=\"TCGPLAYER\">TCGPlayer</Select.Item>\n              <Select.Item value=\"MANAPOOL\">ManaPool</Select.Item>\n            </Select.Content>\n          </Select.Root>\n          {/* Status filter */}\n        </Flex>\n      </Flex>\n      \n      <Table.Root>\n        <Table.Header>\n          <Table.Row>\n            <Table.ColumnHeaderCell>Order ID</Table.ColumnHeaderCell>\n            <Table.ColumnHeaderCell>Marketplace</Table.ColumnHeaderCell>\n            <Table.ColumnHeaderCell>Buyer</Table.ColumnHeaderCell>\n            <Table.ColumnHeaderCell>Status</Table.ColumnHeaderCell>\n            <Table.ColumnHeaderCell>Total</Table.ColumnHeaderCell>\n            <Table.ColumnHeaderCell>Date</Table.ColumnHeaderCell>\n          </Table.Row>\n        </Table.Header>\n        <Table.Body>\n          {orders.map(order => (\n            <Table.Row key={order.id}>\n              {/* ... */}\n            </Table.Row>\n          ))}\n        </Table.Body>\n      </Table.Root>\n    </div>\n  );\n}\n```",
         "testStrategy": "Verify orders display correctly from database. Test filter functionality. Test sort functionality. Verify marketplace badges display correctly. Test pagination.",
         "priority": "medium",
         "dependencies": [
-          8,
-          1
+          "8",
+          "1"
         ],
         "status": "pending",
         "subtasks": [
@@ -411,7 +424,8 @@
             "dependencies": [],
             "details": "Create `packages/web/src/app/dashboard/layout.tsx` as the shared layout for all dashboard pages. Use Radix UI Themes components (Box, Flex, Text) and Tailwind CSS for styling. Implement a sidebar navigation with links to Orders (/dashboard/orders), Inventory (/dashboard/inventory), Products (/dashboard/products), and Settings (/dashboard/settings). The sidebar should be fixed-width (240px) on desktop and collapsible on mobile. Include the Radix Theme provider wrapper if not already in root layout. Add active state highlighting for current route using Next.js usePathname hook. Create marketplace icon components for TCGPlayer and ManaPool that will be reused in the orders table.",
             "status": "pending",
-            "testStrategy": "Verify layout renders with sidebar on /dashboard routes. Test navigation links route correctly. Test responsive behavior collapses sidebar on mobile viewports. Verify active state highlights current route."
+            "testStrategy": "Verify layout renders with sidebar on /dashboard routes. Test navigation links route correctly. Test responsive behavior collapses sidebar on mobile viewports. Verify active state highlights current route.",
+            "parentId": "undefined"
           },
           {
             "id": 2,
@@ -420,7 +434,8 @@
             "dependencies": [],
             "details": "Create `packages/web/src/app/api/orders/route.ts` as a GET endpoint that queries the orders table. Accept query parameters: marketplace (TCGPLAYER | MANAPOOL | INTERNAL | all), status (PENDING | PRINTED | SHIPPED | CANCELLED | all), sortOrder (asc | desc, default desc), page (number, default 1), limit (number, default 20). Use Drizzle ORM to build the query with dynamic where clauses based on filters. Include count query for total results to support pagination UI. Return JSON response with shape: { orders: Order[], total: number, page: number, totalPages: number }. Join with order_lines to include item count per order. Implement proper error handling with appropriate HTTP status codes.",
             "status": "pending",
-            "testStrategy": "Test API returns orders without filters. Test marketplace filter returns only matching orders. Test status filter works correctly. Test pagination returns correct page and total counts. Test sorting by date in both directions."
+            "testStrategy": "Test API returns orders without filters. Test marketplace filter returns only matching orders. Test status filter works correctly. Test pagination returns correct page and total counts. Test sorting by date in both directions.",
+            "parentId": "undefined"
           },
           {
             "id": 3,
@@ -432,7 +447,8 @@
             ],
             "details": "Create `packages/web/src/components/orders/OrdersTable.tsx` using Radix UI Table components (Table.Root, Table.Header, Table.Body, Table.Row, Table.Cell, Table.ColumnHeaderCell). Display columns: Marketplace (icon), Order ID (link to detail), Buyer Name, Status (Badge with color coding), Total (formatted currency), Date (formatted relative or absolute). Implement status badge color mapping: PENDING=yellow, PRINTED=blue, SHIPPED=green, CANCELLED=red using Radix Badge component with color prop. Create clickable rows that navigate to `/dashboard/orders/[id]` for order details. Format totalCents as USD currency ($XX.XX). Format createdAt date using relative format (e.g., '2 hours ago') with absolute date on hover tooltip.",
             "status": "pending",
-            "testStrategy": "Verify table renders all columns correctly. Test status badges show correct colors for each status. Test clicking row navigates to order detail page. Verify currency formatting displays correctly. Test date formatting shows relative time."
+            "testStrategy": "Verify table renders all columns correctly. Test status badges show correct colors for each status. Test clicking row navigates to order detail page. Verify currency formatting displays correctly. Test date formatting shows relative time.",
+            "parentId": "undefined"
           },
           {
             "id": 4,
@@ -443,7 +459,8 @@
             ],
             "details": "Create `packages/web/src/components/orders/OrderFilters.tsx` using Radix UI Select components. Include marketplace filter with options: All, TCGPlayer, ManaPool, Internal. Include status filter with options: All, Pending, Printed, Shipped, Cancelled. Add sort toggle button for date ascending/descending using Radix IconButton with ArrowUp/ArrowDown icons. Use Next.js useSearchParams and useRouter to read/update URL query parameters when filters change. Implement URL-based state so filters persist on page refresh and are shareable. Style with Tailwind CSS using Flex layout with gap-3 spacing. Place filters in a Card component above the table for visual grouping.",
             "status": "pending",
-            "testStrategy": "Test changing marketplace filter updates URL params and filters table. Test status filter updates URL and shows correct orders. Test sort toggle reverses order of results. Verify filters persist on page refresh. Test clearing filters returns to default view."
+            "testStrategy": "Test changing marketplace filter updates URL params and filters table. Test status filter updates URL and shows correct orders. Test sort toggle reverses order of results. Verify filters persist on page refresh. Test clearing filters returns to default view.",
+            "parentId": "undefined"
           },
           {
             "id": 5,
@@ -456,156 +473,161 @@
             ],
             "details": "Create `packages/web/src/app/dashboard/orders/page.tsx` as an async Server Component. Read searchParams for current filters (marketplace, status, sortOrder, page). Fetch orders using the API route or direct Drizzle query (server component can query DB directly). Pass data to OrdersTable client component. Create `packages/web/src/components/orders/Pagination.tsx` using Radix UI Button components with Previous/Next and page number buttons. Calculate page range based on total results and current page. Include page info text showing 'Page X of Y (Z total orders)'. Handle empty states with appropriate messaging ('No orders found' with filter context). Add loading state using React Suspense with skeleton table rows. Implement error boundary for graceful error handling.",
             "status": "pending",
-            "testStrategy": "Verify page loads with orders from database. Test pagination navigation updates page content. Test empty state displays when no orders match filters. Verify loading skeleton shows during data fetch. Test error state displays gracefully on API failure."
+            "testStrategy": "Verify page loads with orders from database. Test pagination navigation updates page content. Test empty state displays when no orders match filters. Verify loading skeleton shows during data fetch. Test error state displays gracefully on API failure.",
+            "parentId": "undefined"
           }
         ]
       },
       {
-        "id": 23,
+        "id": "23",
         "title": "Build Dashboard UI - Order Detail and Fulfillment View",
         "description": "Create the order detail page with line items display, address verification, shipping label generation, and one-click fulfillment workflow.",
         "details": "Create `packages/web/src/app/dashboard/orders/[id]/page.tsx`. Display order header with buyer info and shipping address. Show order lines with product images, names, conditions, and locations (for pick list). Implement 'Buy Postage' button that triggers EasyPost flow. Show address verification status with override option. Display tracking info and label preview after purchase. Add 'Print Label' and 'Print Pull Sheet' buttons.\n\n```tsx\nexport default async function OrderDetailPage({ params }) {\n  const order = await getOrderWithFullDetails(params.id);\n  \n  return (\n    <div className=\"p-6\">\n      <Card className=\"mb-6\">\n        <Heading>Order {order.marketplaceOrderId}</Heading>\n        <Badge>{order.marketplace}</Badge>\n        <Badge color={statusColors[order.status]}>{order.status}</Badge>\n      </Card>\n      \n      {/* Shipping Address Card */}\n      <Card className=\"mb-6\">\n        <Heading size=\"3\">Shipping Address</Heading>\n        <AddressDisplay address={order.shippingAddress} />\n        {order.status === 'PENDING' && (\n          <Button onClick={() => handleBuyPostage(order.id)}>\n            Buy Postage\n          </Button>\n        )}\n      </Card>\n      \n      {/* Order Lines / Pull List */}\n      <Card>\n        <Heading size=\"3\">Items ({order.orderLines.length})</Heading>\n        <Table.Root>\n          {order.orderLines.map(line => (\n            <Table.Row key={line.id}>\n              <Table.Cell>\n                <img src={line.product.imageUrl} className=\"w-12\" />\n              </Table.Cell>\n              <Table.Cell>{line.product.name}</Table.Cell>\n              <Table.Cell>{line.sku.condition} / {line.sku.variant}</Table.Cell>\n              <Table.Cell>\n                <Badge>{line.location?.coordString}</Badge>\n              </Table.Cell>\n              <Table.Cell>{line.quantity}</Table.Cell>\n            </Table.Row>\n          ))}\n        </Table.Root>\n      </Card>\n      \n      {/* Tracking / Label Section */}\n      {order.trackingNumber && (\n        <Card>\n          <Text>Tracking: {order.trackingNumber}</Text>\n          <Button onClick={() => printLabel(order.id)}>Print Label</Button>\n        </Card>\n      )}\n    </div>\n  );\n}\n```",
         "testStrategy": "Verify order details load correctly. Test Buy Postage flow with mocked EasyPost. Verify address verification feedback. Test print buttons create print jobs. Test status transitions.",
         "priority": "medium",
         "dependencies": [
-          19,
-          22
+          "19",
+          "22"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 24,
+        "id": "24",
         "title": "Implement Marketplace Fulfillment Sync (Push Tracking)",
         "description": "Create the background jobs that push tracking information back to TCGPlayer and ManaPool when orders are marked as shipped.",
         "details": "Create database trigger or application hook that fires when order status changes to SHIPPED. For ManaPool orders, call `POST https://manapool.com/api/v1/orders/{order_id}/fulfill` with tracking number and carrier. For TCGPlayer orders, enqueue a Puppeteer job to the worker that uses the session manager to submit tracking via the undocumented API. Handle failures gracefully with retry logic.\n\n```typescript\n// packages/web/src/services/fulfillment-sync.ts\nexport async function syncFulfillmentToMarketplace(orderId: string) {\n  const order = await db.select().from(ordersTable).where(eq(ordersTable.id, orderId)).limit(1);\n  \n  if (!order.trackingNumber) return;\n  \n  if (order.marketplace === 'MANAPOOL') {\n    await fetch(`https://manapool.com/api/v1/orders/${order.marketplaceOrderId}/fulfill`, {\n      method: 'POST',\n      headers: {\n        'Authorization': `Bearer ${process.env.MANAPOOL_API_KEY}`,\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify({\n        tracking_number: order.trackingNumber,\n        carrier: 'USPS',\n      }),\n    });\n  } else if (order.marketplace === 'TCGPLAYER') {\n    // Queue Puppeteer job for TCGPlayer fulfillment\n    await tcgplayerOrdersQueue.add('fulfill', {\n      orderNumber: order.marketplaceOrderId,\n      trackingNumber: order.trackingNumber,\n      carrier: 'USPS',\n    });\n  }\n}\n\n// Worker job for TCGPlayer fulfillment\nworker.on('tcgplayer-orders:fulfill', async (job) => {\n  const { orderNumber, trackingNumber, carrier } = job.data;\n  const token = await getValidToken();\n  \n  // POST to TCGPlayer fulfillment endpoint (needs to be discovered)\n  await fetch(`https://order-management-api.tcgplayer.com/orders/${orderNumber}/tracking`, {\n    method: 'POST',\n    headers: { Authorization: `Bearer ${token}` },\n    body: JSON.stringify({ trackingNumber, carrier }),\n  });\n});\n```",
         "testStrategy": "Test ManaPool sync with mock API. Test TCGPlayer sync queues worker job. Verify retry logic on failure. Test partial success scenarios (label purchased but sync failed).",
         "priority": "medium",
         "dependencies": [
-          17,
-          18
+          "17",
+          "18"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 25,
+        "id": "25",
         "title": "Build Inventory Management UI with Chaos Sort Scanner Integration",
         "description": "Create the inventory management dashboard with barcode scanner input support for rapid inventory receiving and location assignment using the Chaos Sort system.",
         "details": "Create `packages/web/src/app/dashboard/inventory/page.tsx`. Implement scanner input field that accepts TCGPlayer product URLs, SKU IDs, or barcode scans. When item scanned: lookup product in database, display current inventory across all locations, allow quantity adjustment. Implement location assignment with coord_string input. Create quick-receive workflow: scan card -> display info -> input quantity -> assign location -> save.\n\n```tsx\nexport default function InventoryPage() {\n  const [scanInput, setScanInput] = useState('');\n  const [currentProduct, setCurrentProduct] = useState(null);\n  \n  const handleScan = async (input: string) => {\n    // Parse input - could be URL, SKU ID, or product ID\n    const productId = parseProductIdFromInput(input);\n    const product = await fetchProductWithInventory(productId);\n    setCurrentProduct(product);\n  };\n  \n  const handleReceive = async (skuId: number, quantity: number, locationCoord: string) => {\n    await fetch('/api/inventory/receive', {\n      method: 'POST',\n      body: JSON.stringify({ skuId, quantity, locationCoord }),\n    });\n    // Refresh current product display\n    handleScan(currentProduct.id);\n  };\n  \n  return (\n    <div className=\"p-6\">\n      <Card className=\"mb-6\">\n        <TextField.Root \n          placeholder=\"Scan or paste product URL/ID...\"\n          value={scanInput}\n          onChange={(e) => setScanInput(e.target.value)}\n          onKeyDown={(e) => e.key === 'Enter' && handleScan(scanInput)}\n        />\n      </Card>\n      \n      {currentProduct && (\n        <>\n          <ProductInfoCard product={currentProduct} />\n          \n          <Card className=\"mt-4\">\n            <Heading size=\"3\">Current Inventory</Heading>\n            {currentProduct.inventory.map(inv => (\n              <Flex key={inv.id} justify=\"between\" align=\"center\">\n                <Text>{inv.location.coordString}</Text>\n                <Text>Qty: {inv.quantity}</Text>\n                <Text>${(inv.listedPriceCents / 100).toFixed(2)}</Text>\n              </Flex>\n            ))}\n          </Card>\n          \n          <ReceiveInventoryForm \n            product={currentProduct}\n            onReceive={handleReceive}\n          />\n        </>\n      )}\n    </div>\n  );\n}\n```",
         "testStrategy": "Test product lookup by various input formats. Test inventory display with multiple locations. Test receive workflow creates inventory record. Test location validation for coord_string format.",
         "priority": "medium",
         "dependencies": [
-          7,
-          22
+          "7",
+          "22"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 26,
+        "id": "26",
         "title": "Implement SKU Fallback Web Scraper for Non-MTG Products",
         "description": "Create the Puppeteer-based scraper that extracts SKU information from TCGPlayer product pages when TCGTracking doesn't have coverage (e.g., Pokemon, Lorcana).",
         "details": "Create `packages/worker/src/jobs/sku-scraper.ts`. When a product is scanned/queried and has no SKUs in database, enqueue a scraper job. Navigate to TCGPlayer product page using stealth Puppeteer. Extract SKU information from the condition selector/pricing grid. Parse out SKU IDs, conditions, variants, and current prices. Insert into skus table. Cache scraped data to avoid repeated scraping.\n\n```typescript\nworker.on('scraper:extract-skus', async (job) => {\n  const { productId } = job.data;\n  \n  const browser = await puppeteer.launch({ headless: true });\n  const page = await browser.newPage();\n  \n  await page.goto(`https://www.tcgplayer.com/product/${productId}`);\n  await page.waitForSelector('.product-details');\n  \n  // Extract SKUs from the pricing grid\n  const skus = await page.evaluate(() => {\n    const results = [];\n    // Parse the condition/printing selector and price listings\n    document.querySelectorAll('.listing-item').forEach(listing => {\n      const skuId = listing.dataset.skuId;\n      const condition = listing.querySelector('.condition').textContent;\n      const price = parseFloat(listing.querySelector('.price').textContent.replace('$', ''));\n      // ... extract variant (foil/non-foil)\n      results.push({ skuId, condition, price });\n    });\n    return results;\n  });\n  \n  await browser.close();\n  \n  // Insert extracted SKUs\n  for (const sku of skus) {\n    await db.insert(skusTable)\n      .values({\n        id: parseInt(sku.skuId),\n        productId,\n        condition: sku.condition,\n        variant: sku.variant,\n        language: 'EN',\n        lowestListingCents: Math.round(sku.price * 100),\n      })\n      .onConflictDoUpdate(...);\n  }\n});\n```",
         "testStrategy": "Run scraper on known Pokemon product. Verify SKUs extracted and inserted. Test with product that has multiple conditions/variants. Verify scraper doesn't break on page layout changes (add resilience).",
         "priority": "medium",
         "dependencies": [
-          16,
-          5
+          "16",
+          "5"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 27,
+        "id": "27",
         "title": "Implement TCGPlayer Inventory Export and Price/Quantity Update",
         "description": "Create workers to export current TCGPlayer inventory and update prices/quantities using the undocumented admin API endpoints.",
         "details": "Create `packages/worker/src/jobs/tcgplayer-inventory.ts`. Implement inventory export: call `POST https://store.tcgplayer.com/admin/pricing/downloadexportcsv` and parse returned CSV. Map exported inventory to our database. Implement inventory update: for changed items, call `POST https://store.tcgplayer.com/admin/product/updateinventory` with the specific URL-encoded form data format per PRD. Batch updates to avoid rate limiting.\n\n```typescript\nworker.on('tcgplayer-inventory:export', async (job) => {\n  const token = await getValidToken();\n  \n  const response = await fetch(\n    'https://store.tcgplayer.com/admin/pricing/downloadexportcsv',\n    {\n      method: 'POST',\n      headers: { Cookie: `session=${token}` },\n    }\n  );\n  \n  const csv = await response.text();\n  const records = parse(csv, { columns: true });\n  \n  for (const record of records) {\n    await db.update(inventoryTable)\n      .set({\n        lastSyncedTcgplayer: new Date(),\n        // Update with TCGPlayer's current quantity/price if needed\n      })\n      .where(eq(inventoryTable.skuId, record.SKU_ID));\n  }\n});\n\nworker.on('tcgplayer-inventory:update', async (job) => {\n  const { updates } = job.data; // Array of { skuId, quantity, priceCents }\n  const token = await getValidToken();\n  \n  // Build URL-encoded form data per PRD format\n  const formData = new URLSearchParams();\n  updates.forEach((update, i) => {\n    formData.append(`productQuantityPrices[${i}][SKU]`, update.skuId);\n    formData.append(`productQuantityPrices[${i}][Quantity]`, update.quantity);\n    formData.append(`productQuantityPrices[${i}][Price]`, (update.priceCents / 100).toFixed(2));\n  });\n  \n  await fetch('https://store.tcgplayer.com/admin/product/updateinventory', {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/x-www-form-urlencoded',\n      Cookie: `session=${token}`,\n    },\n    body: formData,\n  });\n});\n```",
         "testStrategy": "Export inventory and verify CSV parsing. Test update with single SKU. Test batch update with multiple SKUs. Verify form data encoding matches expected format.",
         "priority": "medium",
         "dependencies": [
-          16,
-          7
+          "16",
+          "7"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 28,
+        "id": "28",
         "title": "Implement Pull Sheet and Packing Slip Generation",
         "description": "Create endpoints and jobs to generate pull sheets and packing slips using TCGPlayer's native PDF generation or custom templates.",
         "details": "Create `packages/web/src/app/api/orders/[id]/pull-sheet/route.ts`. For TCGPlayer orders, call the native `POST /orders/pull-sheets/export` endpoint. For other marketplaces, generate custom PDF using a library like `@react-pdf/renderer`. Insert generated PDF into print queue. Similarly implement packing slip generation.\n\n```typescript\n// API route for pull sheet\nexport async function POST(request: NextRequest, { params }) {\n  const order = await getOrder(params.id);\n  \n  let pdfBuffer: Buffer;\n  \n  if (order.marketplace === 'TCGPLAYER') {\n    // Use native TCGPlayer pull sheet\n    const token = await getTcgplayerToken();\n    const response = await fetch(\n      'https://order-management-api.tcgplayer.com/orders/pull-sheets/export',\n      {\n        method: 'POST',\n        headers: { Authorization: `Bearer ${token}` },\n        body: JSON.stringify({\n          orderNumbers: [order.marketplaceOrderId],\n          timezoneOffset: -5,\n        }),\n      }\n    );\n    pdfBuffer = Buffer.from(await response.arrayBuffer());\n  } else {\n    // Generate custom pull sheet\n    pdfBuffer = await generateCustomPullSheet(order);\n  }\n  \n  // Upload PDF to temporary storage and create print job\n  const url = await uploadToStorage(pdfBuffer, `pull-sheet-${order.id}.pdf`);\n  \n  await db.insert(printJobsTable).values({\n    jobType: 'PULL_SHEET',\n    payload: { url },\n    orderId: order.id,\n    printerTarget: 'LASER_MAIN',\n  });\n  \n  return NextResponse.json({ status: 'queued' });\n}\n```",
         "testStrategy": "Generate pull sheet for TCGPlayer order. Generate custom pull sheet for ManaPool order. Verify PDF content is readable. Test print job creation.",
         "priority": "low",
         "dependencies": [
-          17,
-          9
+          "17",
+          "9"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 29,
+        "id": "29",
         "title": "Set Up Cron Job Scheduling for ETL Workers",
         "description": "Configure BullMQ repeatable jobs for scheduled catalog syncs based on environment configuration.",
         "details": "Create `packages/worker/src/scheduler.ts`. Read cron expressions from environment variables (`CRON_TCGCSV_SYNC`, `CRON_TCGTRACKING_SYNC`). Configure BullMQ repeatable jobs using these schedules. Implement startup logic to register scheduled jobs. Add job deduplication to prevent duplicate runs if worker restarts.\n\n```typescript\n// packages/worker/src/scheduler.ts\nimport { Queue } from 'bullmq';\n\nexport async function setupScheduledJobs() {\n  const catalogSyncQueue = new Queue('catalog-sync', { connection: redisConnection });\n  \n  // TCGCSV sync schedule (e.g., daily at 2am)\n  const tcgcsvCron = process.env.CRON_TCGCSV_SYNC || '0 2 * * *';\n  await catalogSyncQueue.add(\n    'tcgcsv-sync',\n    { categories: process.env.SUPPORTED_CATEGORIES.split(',').map(Number) },\n    {\n      repeat: { cron: tcgcsvCron },\n      jobId: 'tcgcsv-daily-sync', // Prevents duplicates\n    }\n  );\n  \n  // TCGTracking enrichment (e.g., weekly)\n  const tcgtrackingCron = process.env.CRON_TCGTRACKING_SYNC || '0 3 * * 0';\n  await catalogSyncQueue.add(\n    'tcgtracking-sync',\n    {},\n    {\n      repeat: { cron: tcgtrackingCron },\n      jobId: 'tcgtracking-weekly-sync',\n    }\n  );\n  \n  // TCGPlayer order polling (every 5 minutes)\n  await catalogSyncQueue.add(\n    'tcgplayer-orders-poll',\n    {},\n    {\n      repeat: { every: 5 * 60 * 1000 },\n      jobId: 'tcgplayer-orders-poll',\n    }\n  );\n  \n  console.log('Scheduled jobs registered');\n}\n```",
         "testStrategy": "Verify jobs appear in queue with correct cron patterns. Test job execution at scheduled time (mock time or use short interval). Verify no duplicate jobs created on restart.",
         "priority": "medium",
         "dependencies": [
-          13,
-          15
+          "13",
+          "15"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 30,
+        "id": "30",
         "title": "Implement Comprehensive Error Handling and Monitoring",
         "description": "Add error tracking, logging, and monitoring across all services with alerting for critical failures.",
         "details": "Integrate error tracking (e.g., Sentry) in both web and worker packages. Implement structured logging with levels (info, warn, error). Add health check endpoints for each service. Create dashboard for job queue monitoring. Set up alerts for: failed jobs exceeding threshold, print queue backup, auth token failures.\n\n```typescript\n// packages/shared/src/lib/logger.ts\nimport pino from 'pino';\n\nexport const logger = pino({\n  level: process.env.LOG_LEVEL || 'info',\n  transport: process.env.NODE_ENV === 'development' \n    ? { target: 'pino-pretty' } \n    : undefined,\n});\n\n// packages/web/src/app/api/health/route.ts\nexport async function GET() {\n  const checks = {\n    database: await checkDatabase(),\n    redis: await checkRedis(),\n    timestamp: new Date().toISOString(),\n  };\n  \n  const allHealthy = Object.values(checks).every(v => v === true || typeof v === 'string');\n  \n  return NextResponse.json(checks, { status: allHealthy ? 200 : 503 });\n}\n\n// Worker job failure handler\nworker.on('failed', (job, err) => {\n  logger.error({ jobId: job.id, jobName: job.name, error: err.message }, 'Job failed');\n  Sentry.captureException(err, { extra: { jobId: job.id, jobData: job.data } });\n  \n  // Alert if critical job type\n  if (['tcgplayer-auth', 'tcgplayer-orders'].includes(job.name)) {\n    sendAlert(`Critical job ${job.name} failed: ${err.message}`);\n  }\n});\n```",
         "testStrategy": "Trigger intentional errors and verify they're logged/tracked. Check health endpoint reflects service status. Verify alerts fire for critical failures. Test log output format.",
         "priority": "low",
         "dependencies": [
-          17,
-          18,
-          19,
-          21
+          "17",
+          "18",
+          "19",
+          "21"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 31,
+        "id": "31",
         "title": "Create Test TCGPlayer Auth Script for Validation",
         "description": "Build the standalone manual test script for validating TCGPlayer session capture as specified in the PRD verification plan.",
         "details": "Create `scripts/test-tcg-auth.ts` as specified in PRD. Launch Puppeteer with `headless: false` so developer can observe DataDome navigation. Log the captured bearer token to console. Test the token by making a curl-equivalent request to the `/orders` endpoint. Output success/failure status.\n\n```typescript\n// scripts/test-tcg-auth.ts\nimport puppeteer from 'puppeteer-extra';\nimport StealthPlugin from 'puppeteer-extra-plugin-stealth';\n\npuppeteer.use(StealthPlugin());\n\nasync function testAuth() {\n  console.log('Launching browser (visible mode)...');\n  \n  const browser = await puppeteer.launch({ \n    headless: false,\n    defaultViewport: null,\n  });\n  \n  const page = await browser.newPage();\n  let capturedToken: string | null = null;\n  \n  await page.setRequestInterception(true);\n  page.on('request', (request) => {\n    const auth = request.headers()['authorization'];\n    if (request.url().includes('order-management-api') && auth) {\n      capturedToken = auth.replace('Bearer ', '');\n      console.log('\\n=== TOKEN CAPTURED ===');\n      console.log(capturedToken);\n      console.log('======================\\n');\n    }\n    request.continue();\n  });\n  \n  console.log('Navigating to login...');\n  await page.goto('https://store.tcgplayer.com/admin/login');\n  \n  // Wait for user to complete login (or automate with env vars)\n  console.log('Waiting for login completion...');\n  await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 120000 });\n  \n  await page.goto('https://store.tcgplayer.com/admin/orders');\n  await new Promise(resolve => setTimeout(resolve, 5000));\n  \n  if (capturedToken) {\n    console.log('Testing token against /orders endpoint...');\n    const response = await fetch(\n      'https://order-management-api.tcgplayer.com/orders/search',\n      {\n        method: 'POST',\n        headers: { Authorization: `Bearer ${capturedToken}` },\n        body: JSON.stringify({ page: 1, pageSize: 1 }),\n      }\n    );\n    console.log(`API Response Status: ${response.status}`);\n    if (response.ok) {\n      console.log('SUCCESS: Token is valid!');\n    } else {\n      console.log('FAILURE: Token rejected');\n    }\n  }\n  \n  await browser.close();\n}\n\ntestAuth().catch(console.error);\n```",
         "testStrategy": "Run script manually and observe browser navigation. Complete login process. Verify token is logged. Verify API test succeeds with valid token.",
         "priority": "high",
         "dependencies": [
-          16
+          "16"
         ],
         "status": "pending",
         "subtasks": []
       },
       {
-        "id": 32,
+        "id": "32",
         "title": "Create Hardware Print Test Endpoint and Validation",
         "description": "Build the test endpoint and validation workflow for verifying the hardware daemon can successfully receive and print jobs.",
         "details": "Create `packages/web/src/app/api/internal/test-print/route.ts` that pushes a test label to the print queue. The daemon should pick it up and print within 5 seconds as per PRD. Include test label generation with timestamp for verification.\n\n```typescript\n// POST /api/internal/test-print\nexport async function POST(request: NextRequest) {\n  const { printerTarget } = await request.json();\n  \n  // Generate test label content (simple text or ZPL)\n  const testLabel = generateTestLabel(new Date().toISOString());\n  \n  // Insert into print queue\n  const [job] = await db.insert(printJobsTable)\n    .values({\n      jobType: 'LABEL',\n      payload: { content: testLabel, format: 'ZPL' },\n      printerTarget: printerTarget || 'BROTHER_QL1100',\n    })\n    .returning();\n  \n  return NextResponse.json({ \n    jobId: job.id, \n    message: 'Test print job queued. Expect print within 5 seconds if daemon is running.' \n  });\n}\n\nfunction generateTestLabel(timestamp: string): string {\n  return `\n    ^XA\n    ^FO50,50^ADN,36,20^FDTest Print^FS\n    ^FO50,100^ADN,24,12^FD${timestamp}^FS\n    ^FO50,150^ADN,18,10^FDIf you see this, daemon works!^FS\n    ^XZ\n  `;\n}\n```",
         "testStrategy": "Call test endpoint. Verify job created in queue. Run daemon locally. Observe physical print output (or mock printer). Verify completion callback marks job done.",
         "priority": "medium",
         "dependencies": [
-          20,
-          21
+          "20",
+          "21"
         ],
         "status": "pending",
         "subtasks": []
       }
     ],
     "metadata": {
-      "created": "2026-02-23T08:07:31.630Z",
-      "updated": "2026-02-23T08:07:31.630Z",
-      "description": "Tasks for master context"
+      "version": "1.0.0",
+      "lastModified": "2026-02-23T09:01:43.704Z",
+      "taskCount": 32,
+      "completedCount": 1,
+      "tags": [
+        "master"
+      ]
     }
   }
 }
\ No newline at end of file
diff --git a/claude-progress.txt b/claude-progress.txt
index 85408ee..a616fb7 100644
--- a/claude-progress.txt
+++ b/claude-progress.txt
@@ -82,9 +82,137 @@ All 6 core tables defined as Drizzle schemas with full relations:
 - packages/worker/: BullMQ entry point, Redis config
 - packages/daemon/: Hardware daemon entry point
 
+---
+
+## Task 1 Implementation (Agent: Task-1 Coding Agent)
+# Date: 2026-02-23
+
+### What Was Implemented
+- **Fixed dashboard 500 error**: The dashboard layout used `<Link>` wrapping `<RadixLink>`
+  which caused an "Invalid <Link> with <a> child" error in Next.js 15. Replaced with
+  plain `<Link>` using Tailwind CSS classes for styling.
+- **Created reusable Radix UI Button component**: Added `packages/web/src/components/ui/Button.tsx`
+  wrapping Radix UI's Button with a `fullWidth` prop and proper TypeScript types.
+  Added barrel export in `packages/web/src/components/ui/index.ts`.
+- **Updated homepage**: Refactored `page.tsx` to use the custom Button component via the
+  `@/*` path alias, demonstrating proper Radix UI component rendering.
+- **Fixed Next.js lockfile warning**: Added `outputFileTracingRoot` to `next.config.ts`
+  pointing to the monorepo root.
+- **Configured ESLint flat config**: Created `eslint.config.js` with ESLint 9 flat config
+  format, TypeScript support via `@typescript-eslint`, Prettier compatibility, and proper
+  ignores for generated files (`next-env.d.ts`, `.next/`, etc.).
+- **Installed ESLint plugins**: Added `@typescript-eslint/eslint-plugin`,
+  `@typescript-eslint/parser`, `eslint-config-prettier`, `eslint-plugin-prettier`,
+  and `eslint-config-next` as dev dependencies.
+- **Updated .gitignore**: Added `.eslintignore` and `eslint.config.mjs` to gitignore.
+
+### Verification Results
+- `npm install` ✅ — All dependencies install cleanly
+- `npm run dev` ✅ — Dev server starts on port 3000 with zero errors
+- `npm run build` ✅ — Production build succeeds, all pages statically generated
+- TypeScript typecheck ✅ — All 4 packages (shared, web, worker, daemon) pass `tsc --noEmit`
+- Tailwind CSS purge ✅ — Production CSS is 5.6KB (only used utilities included)
+- Radix UI rendering ✅ — Custom Button component renders correctly on homepage
+- ESLint ✅ — `npm run lint` passes with zero errors
+- All pages render ✅ — Homepage, /dashboard/orders, /dashboard/inventory all return 200
+- Zero console errors ✅ — No server-side or client-side errors
+
+### What the Tester Should Pay Attention To
+1. Run `npm install` first — dependencies must be installed
+2. Run `npm run dev` and visit http://localhost:3000 — homepage should show "Zman TCG Fulfillment"
+   with a blue "Go to Dashboard" button (this is the custom Radix UI Button component)
+3. Click "Go to Dashboard" — should navigate to /dashboard/orders with sidebar navigation
+4. Run `npm run build` — should compile successfully with no errors
+5. Run `npm run lint` — should pass with 0 errors (there are 2 harmless warnings:
+   ESLint 9 `.eslintignore` deprecation, and module type performance note)
+6. All 4 packages should pass typecheck: run each with
+   `npm run typecheck --workspace=@zman-tcg/[package-name]`
+
 ## Ready for Next Agents
 The project is in a clean state. Next agents should:
 1. Run `npm install` to install all dependencies
 2. Copy `.env.example` to `.env.local` and configure credentials
 3. Run `npm run db:push` to create database tables
 4. Pick up tasks from task-master starting with the highest priority pending items
+
+---
+
+## Task 2 Implementation (Agent: Task-2 Coding Agent)
+# Date: 2026-02-23
+
+### What Was Implemented
+- **Dual-adapter Drizzle database connections**: Updated `packages/shared/src/db/index.ts`
+  to support three connection strategies:
+  1. `createServerlessDb()` — Uses `@neondatabase/serverless` WebSocket pool adapter for
+     Next.js API routes and serverless functions (Vercel, Netlify, Edge)
+  2. `createHttpDb()` — Uses Neon's HTTP transport for one-shot queries in edge functions
+  3. `createDb()` — Uses standard `node-postgres` Pool for persistent processes (worker, daemon)
+
+- **Serverless-optimized connection pooling**: Each adapter has tuned pool settings:
+  - Serverless: max 10 connections, 10s idle timeout (ephemeral function lifecycle)
+  - Persistent: max 20 connections, 30s idle timeout (long-running worker processes)
+  - Configurable via `PoolConfig` interface (maxConnections, idleTimeoutMs, connectionTimeoutMs)
+
+- **Web package DB singleton**: Created `packages/web/src/db/index.ts` with a lazy-initialized
+  singleton pattern using `createServerlessDb()`. The `getDb()` function initializes the
+  connection on first access to minimize cold start overhead.
+
+- **Enhanced drizzle.config.ts**: Updated root config to:
+  - Auto-load `.env.local` (Next.js convention) for DATABASE_URL
+  - Enable verbose logging for development
+  - Enable strict mode for destructive change confirmation
+
+- **Updated shared package exports**: `packages/shared/src/index.ts` now exports all three
+  connection factories plus their TypeScript types (Database, ServerlessDatabase, HttpDatabase,
+  PoolConfig).
+
+- **Database connection test script**: Created `scripts/test-db-connection.ts` that verifies:
+  1. All schema types are correctly inferred at compile time (CategoryInsert, ProductSelect, etc.)
+  2. All 10 tables are properly defined
+  3. All factory functions are available
+  4. Live database connection (when DATABASE_URL is configured)
+  Added `npm run db:test` script to package.json.
+
+- **Generated initial migration**: Running `npm run db:generate` produces
+  `packages/shared/drizzle/0000_glamorous_rawhide_kid.sql` with all 10 tables, 4 pgEnums,
+  15 foreign keys, and 14 indexes.
+
+- **Enhanced migration runner**: Updated `packages/shared/src/db/migrate.ts` with better
+  error messages and password-masked logging.
+
+### Files Created/Modified
+- `packages/shared/src/db/index.ts` — Rewrote with 3 adapter factories + PoolConfig
+- `packages/shared/src/index.ts` — Updated exports for new types/functions
+- `packages/web/src/db/index.ts` — NEW: Serverless DB singleton for Next.js
+- `drizzle.config.ts` — Enhanced with .env.local loading, verbose/strict mode
+- `packages/shared/src/db/migrate.ts` — Improved error handling and logging
+- `scripts/test-db-connection.ts` — NEW: Comprehensive type + connection test
+- `package.json` — Added `db:test` script
+- `packages/shared/drizzle/` — Generated migration files
+
+### Verification Results
+- `npm run db:generate` ✅ — Generates migration with 10 tables, 4 enums, 15 FKs, 14 indexes
+- `npm run db:test` ✅ — All static checks pass (types, schema definitions, factory functions)
+- `npm run typecheck --workspace=@zman-tcg/shared` ✅ — Compiles cleanly
+- `npm run typecheck --workspace=@zman-tcg/web` ✅ — Compiles cleanly
+- `npm run typecheck --workspace=@zman-tcg/worker` ✅ — Compiles cleanly
+- `npm run build` ✅ — Next.js production build succeeds
+- `npm run dev` ✅ — Dev server starts with zero errors
+
+### What the Tester Should Pay Attention To
+1. Run `npm run db:generate` — should produce migration SQL in `packages/shared/drizzle/`
+   with all 10 tables, 4 enums, foreign keys, and indexes
+2. Run `npm run db:test` — should show all 4 checks passing:
+   - TypeScript type inference
+   - Schema table definitions (10 tables)
+   - Database factory functions
+   - Connection test (skipped without real DATABASE_URL, which is expected)
+3. Run `npm run typecheck --workspace=@zman-tcg/shared` — should pass cleanly
+4. Run `npm run typecheck --workspace=@zman-tcg/web` — should pass cleanly
+5. Run `npm run typecheck --workspace=@zman-tcg/worker` — should pass cleanly
+6. Run `npm run build` — should succeed with no errors
+7. Verify `packages/web/src/db/index.ts` exists and uses `createServerlessDb`
+8. Verify `packages/shared/src/db/index.ts` exports all three adapters:
+   `createDb`, `createServerlessDb`, `createHttpDb`
+9. If a real DATABASE_URL (Neon/Supabase) is available, set it in `.env.local`
+   and re-run `npm run db:test` — should show "Connection: SUCCESS"
diff --git a/drizzle.config.ts b/drizzle.config.ts
index df964be..efc0b99 100644
--- a/drizzle.config.ts
+++ b/drizzle.config.ts
@@ -1,10 +1,64 @@
 import { defineConfig } from 'drizzle-kit';
 
+// Load .env.local for local development
+// drizzle-kit uses dotenv internally, but we need to check for .env.local
+import * as fs from 'fs';
+import * as path from 'path';
+
+// Try loading from .env.local first (Next.js convention), then .env
+const envLocalPath = path.resolve(process.cwd(), '.env.local');
+const envPath = path.resolve(process.cwd(), '.env');
+
+if (fs.existsSync(envLocalPath)) {
+  const envContent = fs.readFileSync(envLocalPath, 'utf-8');
+  for (const line of envContent.split('\n')) {
+    const trimmed = line.trim();
+    if (trimmed && !trimmed.startsWith('#')) {
+      const eqIndex = trimmed.indexOf('=');
+      if (eqIndex > 0) {
+        const key = trimmed.slice(0, eqIndex).trim();
+        const value = trimmed.slice(eqIndex + 1).trim();
+        if (!process.env[key]) {
+          process.env[key] = value;
+        }
+      }
+    }
+  }
+} else if (fs.existsSync(envPath)) {
+  const envContent = fs.readFileSync(envPath, 'utf-8');
+  for (const line of envContent.split('\n')) {
+    const trimmed = line.trim();
+    if (trimmed && !trimmed.startsWith('#')) {
+      const eqIndex = trimmed.indexOf('=');
+      if (eqIndex > 0) {
+        const key = trimmed.slice(0, eqIndex).trim();
+        const value = trimmed.slice(eqIndex + 1).trim();
+        if (!process.env[key]) {
+          process.env[key] = value;
+        }
+      }
+    }
+  }
+}
+
 export default defineConfig({
+  // Use PostgreSQL dialect
   dialect: 'postgresql',
+
+  // Schema files location - all Drizzle table definitions
   schema: './packages/shared/src/schema/*.ts',
+
+  // Output directory for generated migrations
   out: './packages/shared/drizzle',
+
+  // Database credentials from environment
   dbCredentials: {
     url: process.env.DATABASE_URL!,
   },
+
+  // Verbose logging during development
+  verbose: true,
+
+  // Require confirmation for destructive changes
+  strict: true,
 });
diff --git a/package.json b/package.json
index 09f7332..54f2574 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,8 @@
     "db:generate": "drizzle-kit generate",
     "db:push": "drizzle-kit push",
     "db:migrate": "tsx packages/shared/src/db/migrate.ts",
-    "db:studio": "drizzle-kit studio"
+    "db:studio": "drizzle-kit studio",
+    "db:test": "tsx scripts/test-db-connection.ts"
   },
   "devDependencies": {
     "@types/node": "^22.0.0",
diff --git a/packages/shared/src/db/index.ts b/packages/shared/src/db/index.ts
index b5a8f59..f2f7583 100644
--- a/packages/shared/src/db/index.ts
+++ b/packages/shared/src/db/index.ts
@@ -1,20 +1,120 @@
-import { drizzle } from 'drizzle-orm/node-postgres';
-import { Pool } from 'pg';
+import { drizzle as drizzleNodePg } from 'drizzle-orm/node-postgres';
+import { drizzle as drizzleNeonServerless } from 'drizzle-orm/neon-serverless';
+import { drizzle as drizzleNeonHttp } from 'drizzle-orm/neon-http';
+import { Pool as NeonPool, neonConfig } from '@neondatabase/serverless';
+import { neon } from '@neondatabase/serverless';
+import { Pool as PgPool } from 'pg';
 import * as schema from '../schema';
 
+// ============================================================
+// Connection Pool Configuration
+// ============================================================
+export interface PoolConfig {
+  /** Maximum number of connections in the pool */
+  maxConnections?: number;
+  /** Idle timeout in milliseconds before a connection is released */
+  idleTimeoutMs?: number;
+  /** Connection timeout in milliseconds */
+  connectionTimeoutMs?: number;
+}
+
+const DEFAULT_SERVERLESS_POOL_CONFIG: PoolConfig = {
+  maxConnections: 10, // Conservative for serverless burst scenarios
+  idleTimeoutMs: 10000, // Shorter idle timeout for serverless (10s)
+  connectionTimeoutMs: 5000,
+};
+
+const DEFAULT_PERSISTENT_POOL_CONFIG: PoolConfig = {
+  maxConnections: 20, // Higher limit for persistent workers
+  idleTimeoutMs: 30000, // Standard 30s idle timeout
+  connectionTimeoutMs: 5000,
+};
+
+// ============================================================
+// Neon Serverless Database (for Next.js / Vercel / Edge)
+// Uses WebSocket-based connections compatible with serverless
+// ============================================================
+
 /**
- * Create a Drizzle database instance with full schema for type-safe queries.
- * Uses node-postgres Pool for connection management.
+ * Create a Drizzle database instance using the Neon serverless adapter.
+ * Ideal for Next.js API routes, Edge functions, and serverless environments.
+ *
+ * Uses WebSocket-based connections that are compatible with serverless
+ * environments where traditional TCP sockets aren't available.
+ *
+ * @param connectionString - PostgreSQL connection string (Neon format)
+ * @param poolConfig - Optional connection pool configuration
  */
-export function createDb(connectionString: string) {
-  const pool = new Pool({
+export function createServerlessDb(
+  connectionString: string,
+  poolConfig?: Partial<PoolConfig>
+) {
+  const config = { ...DEFAULT_SERVERLESS_POOL_CONFIG, ...poolConfig };
+
+  const pool = new NeonPool({
     connectionString,
-    max: 20, // Max connections in pool
-    idleTimeoutMillis: 30000,
-    connectionTimeoutMillis: 5000,
+    max: config.maxConnections,
+    idleTimeoutMillis: config.idleTimeoutMs,
+    connectionTimeoutMillis: config.connectionTimeoutMs,
   });
 
-  return drizzle(pool, { schema });
+  return drizzleNeonServerless(pool, { schema });
 }
 
+/**
+ * Create a Drizzle database instance using Neon's HTTP transport.
+ * Best for one-shot queries in edge functions / serverless where
+ * connection pooling overhead isn't worth it.
+ *
+ * @param connectionString - PostgreSQL connection string (Neon format)
+ */
+export function createHttpDb(connectionString: string) {
+  const sql = neon(connectionString);
+  return drizzleNeonHttp(sql, { schema });
+}
+
+// ============================================================
+// Standard Node-Postgres Database (for Worker / Daemon)
+// Uses traditional TCP connections for persistent processes
+// ============================================================
+
+/**
+ * Create a Drizzle database instance using the standard node-postgres driver.
+ * Ideal for persistent processes like the BullMQ worker and hardware daemon.
+ *
+ * Uses traditional TCP socket connections with full connection pooling.
+ *
+ * @param connectionString - PostgreSQL connection string
+ * @param poolConfig - Optional connection pool configuration
+ */
+export function createDb(
+  connectionString: string,
+  poolConfig?: Partial<PoolConfig>
+) {
+  const config = { ...DEFAULT_PERSISTENT_POOL_CONFIG, ...poolConfig };
+
+  const pool = new PgPool({
+    connectionString,
+    max: config.maxConnections,
+    idleTimeoutMillis: config.idleTimeoutMs,
+    connectionTimeoutMillis: config.connectionTimeoutMs,
+  });
+
+  return drizzleNodePg(pool, { schema });
+}
+
+// ============================================================
+// Type Exports
+// ============================================================
+
+/** Database instance type for Neon serverless (WebSocket pool) */
+export type ServerlessDatabase = ReturnType<typeof createServerlessDb>;
+
+/** Database instance type for Neon HTTP (one-shot queries) */
+export type HttpDatabase = ReturnType<typeof createHttpDb>;
+
+/** Database instance type for standard node-postgres (persistent) */
 export type Database = ReturnType<typeof createDb>;
+
+// Re-export NeonPool config setter for advanced usage (e.g., WebSocket library override)
+export { neonConfig };
diff --git a/packages/shared/src/db/migrate.ts b/packages/shared/src/db/migrate.ts
index b0122e9..732f389 100644
--- a/packages/shared/src/db/migrate.ts
+++ b/packages/shared/src/db/migrate.ts
@@ -2,14 +2,25 @@ import { drizzle } from 'drizzle-orm/node-postgres';
 import { migrate } from 'drizzle-orm/node-postgres/migrator';
 import { Pool } from 'pg';
 
+/**
+ * Run database migrations using the standard node-postgres driver.
+ * This script is intended to be run via `npm run db:migrate` from
+ * the project root. It reads DATABASE_URL from the environment.
+ *
+ * For serverless databases (Neon/Supabase), this should be run
+ * locally or in CI - NOT from within a serverless function.
+ */
 async function runMigrations() {
   const connectionString = process.env.DATABASE_URL;
   if (!connectionString) {
-    console.error('DATABASE_URL environment variable is required');
+    console.error('ERROR: DATABASE_URL environment variable is required');
+    console.error('Set it in .env.local or export it in your shell');
     process.exit(1);
   }
 
   console.log('Running migrations...');
+  // Mask password in log output for safety
+  console.log(`Target: ${connectionString.replace(/:[^:@]+@/, ':***@')}`);
 
   const pool = new Pool({ connectionString });
   const db = drizzle(pool);
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index aace825..c1a83d1 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -5,6 +5,18 @@
 // Database schema & relations
 export * from './schema';
 
-// Database connection
-export { createDb } from './db';
-export type { Database } from './db';
+// Database connection factories
+export {
+  createDb,
+  createServerlessDb,
+  createHttpDb,
+  neonConfig,
+} from './db';
+
+// Database instance types
+export type {
+  Database,
+  ServerlessDatabase,
+  HttpDatabase,
+  PoolConfig,
+} from './db';
diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json
index 049312d..af8bc20 100644
--- a/packages/web/tsconfig.json
+++ b/packages/web/tsconfig.json
@@ -1,13 +1,25 @@
 {
   "extends": "../../tsconfig.base.json",
   "compilerOptions": {
-    "plugins": [{ "name": "next" }],
+    "plugins": [
+      {
+        "name": "next"
+      }
+    ],
     "baseUrl": "../../",
     "paths": {
-      "@/*": ["./packages/web/src/*"],
-      "@zman-tcg/shared/*": ["./packages/shared/src/*"],
-      "@zman-tcg/shared": ["./packages/shared/src/index.ts"]
-    }
+      "@/*": [
+        "./packages/web/src/*"
+      ],
+      "@zman-tcg/shared/*": [
+        "./packages/shared/src/*"
+      ],
+      "@zman-tcg/shared": [
+        "./packages/shared/src/index.ts"
+      ]
+    },
+    "allowJs": true,
+    "jsx": "preserve"
   },
   "include": [
     "next-env.d.ts",
@@ -15,5 +27,7 @@
     "**/*.tsx",
     ".next/types/**/*.ts"
   ],
-  "exclude": ["node_modules"]
+  "exclude": [
+    "node_modules"
+  ]
 }
