Skip to content

Specification: Utility Extraction

Implementation specification for Part 2 of Component Preparation. Requirements: requirements.md. Verification: verification.md. Detailed code changes: implementation-changes.md.

  • Part 1 (Legacy Cleanup) is complete and committed
  • All checks pass on the current branch (baseline from Part 1)

Groups are independent but the recommended order minimizes risk by starting with the highest-impact, lowest-risk extractions:

  1. Group E — Error handling (1 function, simplest extraction)
  2. Group G — Environment constants (foundation for debug logging)
  3. Group F — Formatters (pure functions, no dependencies)
  4. Group D — Safe serialization (pure functions, browser API only)
  5. Group A — API route infrastructure (highest impact, touches 43 files)
  6. Group B — Lookup parsing (refactor within single file)
  7. Group C — JWT consolidation (touches auth-critical code, highest care)

Each group is one commit. All checks must pass after each commit before proceeding to the next group.

Files: src/lib/errors.ts (new), src/lib/errors.test.ts (new)

  1. Create src/lib/errors.ts with extractErrorMessage(error: unknown): string
  2. Create src/lib/errors.test.ts with cases per REQ-UE-E01
  3. Replace inline error narrowing patterns in API routes and auth handlers with imports from errors.ts
  4. Run all checks

See implementation-changes.md § Group E for file-level details.

Files: src/lib/env-spa.ts (new), src/lib/env-spa.test.ts (new), src/lib/utils.ts (modify)

  1. Create src/lib/env-spa.ts exporting IS_PRODUCTION, IS_STAGE, IS_MOCK_MODE
  2. Create src/lib/env-spa.test.ts per REQ-UE-G03
  3. Update debugLog/debugError/debugWarn in src/lib/utils.ts to import IS_STAGE from env-spa.ts
  4. Replace scattered process.env.NEXT_PUBLIC_* comparisons in consumer files with the exported constants
  5. Run all checks

See implementation-changes.md § Group G for file-level details.

1 Files: src/lib/formatters.ts (new), src/lib/formatters.test.ts (new), src/components/table/columnPresets.tsx (modify), src/lib/ardaClient.ts (modify)

  1. Create src/lib/formatters.ts with formatDate, formatDateTime, formatCurrency, formatQuantity
  2. Create src/lib/formatters.test.ts per REQ-UE-F05
  3. Update columnPresets.tsx to import formatters from formatters.ts (remove inline definitions)
  4. Update ardaClient.ts formatQuantity to import from formatters.ts
  5. Run all checks

See implementation-changes.md § Group F for file-level details.

Files: src/lib/storage.ts (new), src/lib/storage.test.ts (new)

  1. Create src/lib/storage.ts with safeJsonParse, getStorageItem, setStorageItem
  2. Create src/lib/storage.test.ts per REQ-UE-D04
  3. Replace inline localStorage JSON patterns in consumer files with imports from storage.ts
  4. Run all checks

See implementation-changes.md § Group D for file-level details.

Files: src/lib/api-route-utils.ts (new), src/lib/api-route-utils.test.ts (new), 43 route files (modify)

  1. Create src/lib/api-route-utils.ts with generateRequestId, parseUpstreamResponse, forwardAsNextResponse
  2. Create src/lib/api-route-utils.test.ts per REQ-UE-A05
  3. Update each API route handler to import and use the shared functions, removing inline implementations
  4. Run all checks

STOP: This group touches 43 files. After updating all routes, run the full test suite and verify no route handler behavior has changed. Spot-check at least 3 routes across different domains (items, kanban, tenant) to confirm correct request IDs, content-type parsing, and status forwarding.

See implementation-changes.md § Group A for file-level details.

Files: src/lib/ardaClient.ts (modify)

  1. Create the lookupField helper function (exported for testing)
  2. Refactor all 9 lookup functions to delegate to lookupField
  3. Add test cases for lookupField in the appropriate ardaClient.*.test.ts file
  4. Run all checks

See implementation-changes.md § Group B for file-level details.

Files: src/lib/jwt.ts (modify if needed), src/store/thunks/authThunks.ts (modify), src/lib/tokenRefresh.ts (modify), src/store/components/AuthInit.tsx (modify), src/contexts/AuthContext.tsx (modify), src/lib/ardaClient.ts (modify)

  1. Ensure decodeJWTPayload in jwt.ts is client-safe (no NextRequest dependency in the function itself — the import is already separate)
  2. Replace all inline atob(token.split('.')[1]) patterns with decodeJWTPayload imports
  3. Replace all inline token.split('.').length !== 3 checks with a shared isValidJWTFormat function from jwt.ts
  4. Run all checks

STOP: This group modifies auth-critical code paths. After replacement, verify that all auth-related tests pass. Pay special attention to token refresh flows and the AuthInit bootstrap sequence.

See implementation-changes.md § Group C for file-level details.

After all groups are committed, add a single CHANGELOG.md entry under [Unreleased]:

### Changed
- Extracted shared API route utilities (`generateRequestId`,
`parseUpstreamResponse`, `forwardAsNextResponse`) to `src/lib/api-route-utils.ts`,
replacing inline implementations across 43 route handlers
- Consolidated 9 lookup functions in `ardaClient.ts` into parameterized
`lookupField` helper
- Replaced inline JWT decode/validation with shared `jwt.ts` functions
- Extracted `localStorage` JSON utilities to `src/lib/storage.ts`
- Extracted error message narrowing to `src/lib/errors.ts`
- Extracted date/currency/quantity formatters to `src/lib/formatters.ts`
- Extracted client-side environment constants to `src/lib/env-spa.ts`

Run all checks one final time after the CHANGELOG commit:

Terminal window
npm run lint
npx tsc --noEmit
npx jest --no-coverage --watchAll=false --forceExit
npm run build

Each group follows this sequence before moving to the next:

  1. Implement the extraction and its unit tests
  2. Run all local checks:
    Terminal window
    npm run lint
    npx tsc --noEmit
    npx jest --no-coverage --watchAll=false --forceExit
    npm run build
  3. All checks must pass — diagnose and fix any failures before proceeding
  4. Commit the group as a single atomic commit
  5. Only then begin the next group

Do not batch multiple groups into a single commit. Do not begin the next group with uncommitted changes from the previous one.

  • Do not modify any file not listed in the group’s scope.
  • Do not reformat or restructure surrounding code.
  • Preserve all public API signatures — consumers must compile without edits beyond import paths.
  • src/lib/errors.ts + tests created; inline patterns replaced
  • src/lib/env-spa.ts + tests created; utils.ts updated
  • src/lib/formatters.ts + tests created; columnPresets.tsx and ardaClient.ts updated
  • src/lib/storage.ts + tests created; inline patterns replaced
  • src/lib/api-route-utils.ts + tests created; 43 routes updated
  • lookupField extracted in ardaClient.ts; tests added
  • Inline JWT decode/validation replaced with jwt.ts imports
  • CHANGELOG updated
  • All checks pass after every group commit
#QuestionOptionsRecommendationDecision
1Should lookupField be exported (testable directly) or kept private (tested through public functions only)?a) Export for direct testing, b) Keep private, test via public wrappersExport — enables focused unit tests without complex mock setup for 9 wrapperspending
2Should isValidJWTFormat be a new named export from jwt.ts or inlined in decodeJWTPayload?a) Separate named export, b) Internal to decodeJWTPayloadSeparate export — ardaClient.ts uses format validation independently of decodepending
3Should Group A CHANGELOG entry use Changed or Added category?a) Changed (refactoring existing behavior), b) Added (new utility module)Changed — behavior is preserved, only the code organization changespending

Copyright: (c) Arda Systems 2025-2026, All rights reserved