Goal: Component Preparation
Prepare arda-frontend-app for the image upload frontend project by removing
legacy dead code, extracting shared utilities from duplicated patterns, and
adding local development tooling for multi-package workflows.
Context
Section titled “Context”The arda-frontend-app contains a legacy ItemTable component
(src/app/items/ItemTable.tsx) built with @tanstack/react-table that has
been superseded by ItemTableAGGrid. The items page (page.tsx) imports
ItemTableAGGrid; ItemTable is dead code with only its own test file
(src/tests/itemTable.test.tsx) as a consumer. The @tanstack/react-table
dependency should be removed.
The codebase also contains significant code duplication — particularly in the
API route layer (43+ route handlers sharing identical boilerplate) and in
ardaClient.ts (9 near-identical lookup functions). Extracting shared
utilities reduces maintenance burden and establishes clean patterns before
adding new features.
Additionally, the app depends on @arda-cards/design-system (from
ux-prototype) and will soon depend on @arda-cards/api-proxy. Local
development across these packages requires a streamlined workflow that doesn’t
risk modifying package.json. A dev:local script has been developed for
this purpose (see tools/dev-local.sh in arda-frontend-app).
Repositories
Section titled “Repositories”arda-frontend-app(Arda-cards/arda-frontend-app) — remove legacy code, extract utilities, add local development toolingdocumentation(Arda-cards/documentation) — project planning documents
Constraints
Section titled “Constraints”- All checks must pass after each commit —
npm run lint,npx tsc --noEmit,npx jest --no-coverage --watchAll=false --forceExit,npm run build - Parts are ordered — each part establishes a clean baseline for the next
package.jsondev:local entry must not interfere with CI — the script references sibling directories that only exist in worktree layouts; CI doesn’t invokedev:local- CHANGELOG must be updated per repository conventions
Part 1 — Legacy Cleanup
Section titled “Part 1 — Legacy Cleanup”Remove dead code and its dependency to establish a clean baseline.
- Verify no consumers — grep the codebase to confirm
ItemTableand@tanstack/react-tablehave no active consumers beyond the component itself and its test. - Remove
ItemTable.tsx(src/app/items/ItemTable.tsx) - Remove associated test (
src/tests/itemTable.test.tsx) - Remove
@tanstack/react-tabledependency frompackage.jsonvianpm uninstall - Verify AG Grid table (
ItemTableAGGrid) continues to function correctly after removal
Deliverables
Section titled “Deliverables”| # | Deliverable | Location |
|---|---|---|
| 1 | Remove ItemTable.tsx | src/app/items/ItemTable.tsx (delete) |
| 2 | Remove itemTable.test.tsx | src/tests/itemTable.test.tsx (delete) |
| 3 | Remove @tanstack/react-table dependency | package.json (modify) |
Success Criteria
Section titled “Success Criteria”ItemTable.tsxanditemTable.test.tsxdeleted; no references remain.@tanstack/react-tableremoved frompackage.json; no imports remain. Confirmed by grep before removal.ItemTableAGGridcontinues to function correctly.- Full local checks pass: lint, typecheck, unit tests, build.
Part 2 — Utility Extraction
Section titled “Part 2 — Utility Extraction”Extract repeated pure TypeScript patterns into shared utility modules. Each group targets code with strong internal cohesion around the types it manages. Groups are independent and can be reviewed separately.
Group A — API Route Infrastructure (Server)
Section titled “Group A — API Route Infrastructure (Server)”File: src/lib/api-route-utils.ts (new)
The 43+ Next.js API route handlers in src/app/api/arda/ share identical
boilerplate: generate a request ID, build upstream headers, forward the
request, parse the response by content-type, and return a NextResponse.
| Pattern | Occurrences | Est. Reduction |
|---|---|---|
Inline GUID generation (generateGuid) | 43 routes | ~250 lines |
| Response content-type parsing (text → conditional JSON.parse) | 48 routes | ~200 lines |
| Response status → NextResponse mapping | 18+ routes | ~60 lines |
Extracted API:
generateRequestId(): stringparseUpstreamResponse(response: Response): Promise<{ data: unknown; contentType: string }>forwardAsNextResponse(upstream: Response, data: unknown): NextResponse
Est. reduction: ~510 lines
Testing: src/lib/api-route-utils.test.ts (new)
generateRequestId— returns valid UUID v4 format; successive calls produce distinct values.parseUpstreamResponse— returns parsed JSON when content-type isapplication/json; returns{ raw: text }for other content types; handles empty response body.forwardAsNextResponse— maps upstream 2xx to 200; preserves upstream 4xx/5xx status; includesokandstatusfields in body.
Existing route-handler tests are not modified — they continue to exercise the routes end-to-end and now implicitly cover the extracted functions.
Group B — Lookup Response Parsing (Client)
Section titled “Group B — Lookup Response Parsing (Client)”File: src/lib/ardaClient.ts (refactor in place)
All 9 lookup functions (lookupSuppliers, lookupUnits, lookupTypes,
lookupSubtypes, lookupUseCases, lookupFacilities, lookupDepartments,
lookupLocations, lookupSublocations) share the same triple-fallback
parsing logic, differing only by endpoint URL and field name.
| Pattern | Occurrences | Est. Reduction |
|---|---|---|
| Lookup response parsing (3-layer fallback) | 9 functions | ~300 lines |
Type guard filters (typeof x === 'string') | 15+ (within the 9) | (included above) |
Extracted API (private to ardaClient.ts):
lookupField(endpoint: string, fieldName: string, params: URLSearchParams): Promise<string[]>
Est. reduction: ~300 lines
Testing: covered by existing ardaClient.*.test.ts files
- The 9 public lookup functions keep their signatures — existing tests continue to pass unchanged.
- Add a focused test block for
lookupField(the new private-turned-exported helper) covering: plain string array response, named-field response ({ data: { units: [...] } }),results[].namefallback, empty/missing data returns[].
Group C — JWT / Token Consolidation (Mixed)
Section titled “Group C — JWT / Token Consolidation (Mixed)”File: src/lib/jwt.ts (consolidate into existing file)
jwt.ts already provides decodeJWTPayload() and extractTokenFromRequest(),
but client-side files (authThunks.ts, tokenRefresh.ts, AuthInit.tsx,
AuthContext.tsx) inline their own decode and format-validation logic. A
client-safe decodeJWTPayload export (no NextRequest dependency) can serve
both sides.
| Pattern | Occurrences | Est. Reduction |
|---|---|---|
JWT payload decoding (atob(token.split('.')[1])) | 11 places / 4 files | ~20 lines |
JWT format validation (split('.').length !== 3) | 4+ places | ~10 lines |
Action: Replace inline usages with imports from jwt.ts. No new file
needed.
Est. reduction: ~30 lines (small line count, but eliminates a security-sensitive pattern being hand-rolled in multiple places)
Testing: covered by existing jwt.test.ts
decodeJWTPayloadand format validation are already tested injwt.test.ts. No new test file needed.- Verify existing tests in
authThunks/tokenRefresh/AuthInitstill pass after replacing inline decode with the imported function — no changes to those test files.
Group D — Safe Serialization (Client)
Section titled “Group D — Safe Serialization (Client)”File: src/lib/storage.ts (new)
localStorage access and JSON.parse are always used together with
identical try/catch error-swallowing wrappers. Both deal with untrusted
serialized data at the browser boundary.
| Pattern | Occurrences | Est. Reduction |
|---|---|---|
| localStorage JSON get/set with try/catch | 24+ places | ~100 lines |
| Safe JSON.parse with fallback | 8+ places | ~30 lines |
Extracted API:
safeJsonParse<T>(text: string, fallback: T): TgetStorageItem<T>(key: string, fallback: T): TsetStorageItem(key: string, value: unknown): void
Est. reduction: ~130 lines
Testing: src/lib/storage.test.ts (new)
safeJsonParse— returns parsed object for valid JSON; returns fallback for malformed JSON; returns fallback for empty string.getStorageItem— returns parsed value when key exists; returns fallback when key is missing; returns fallback when stored value is malformed JSON.setStorageItem— serializes and stores value; handleslocalStoragequota errors without throwing.
Existing tests in consumers (e.g., ItemTableAGGrid, SidebarVisibilityContext)
are not modified — they continue to exercise the call sites end-to-end.
Group E — Error Handling (Mixed)
Section titled “Group E — Error Handling (Mixed)”File: src/lib/errors.ts (new)
Error-to-string narrowing is repeated in both server-side API routes and client-side auth handlers.
| Pattern | Occurrences | Est. Reduction |
|---|---|---|
Error message extraction (instanceof Error ? .message : String(error)) | 20+ places | ~80 lines |
Extracted API:
extractErrorMessage(error: unknown): string
Est. reduction: ~80 lines
Testing: src/lib/errors.test.ts (new)
extractErrorMessage— extracts.messagefromErrorinstances; returns the string directly for string errors; callsString()on other types (numbers, objects,null,undefined).
Group F — Formatters (Client)
Section titled “Group F — Formatters (Client)”File: src/lib/formatters.ts (new)
Date, currency, and quantity formatting currently live in
src/components/table/columnPresets.tsx (an AG Grid file) and are duplicated
in ardaClient.ts. Pure formatting logic has no business being coupled to
AG Grid column definitions.
| Pattern | Occurrences | Est. Reduction |
|---|---|---|
| Date/currency/quantity formatting | 8+ places | ~50 lines |
Extracted API:
formatDate(date: string | undefined): stringformatDateTime(date: string | undefined): stringformatCurrency(value: Money | undefined): stringformatQuantity(quantity: Quantity | undefined): string
Est. reduction: ~50 lines
Testing: src/lib/formatters.test.ts (new)
formatDate— formats ISO string to locale date; returns'-'forundefined.formatDateTime— formats ISO string to locale date+time; returns'-'forundefined.formatCurrency— formatsMoneyas$X.XX CUR; returns'-'forundefinedor null value.formatQuantity— formats asamount unit; returns'-'(or default) forundefined.
Existing tests in columnPresets or AG Grid components are not modified.
Group G — Environment Constants
Section titled “Group G — Environment Constants”Scattered process.env.NEXT_PUBLIC_* comparisons are consolidated into named
boolean constants, split by execution context.
Server file: src/lib/env.ts (existing — already provides BASE_URL,
ARDA_API_KEY, etc.)
Client file: src/lib/env-spa.ts (new)
| Pattern | Occurrences | Est. Reduction |
|---|---|---|
Scattered process.env.NEXT_PUBLIC_* checks | 30+ places | ~50 lines |
Extracted API (env-spa.ts):
const IS_PRODUCTION: booleanconst IS_STAGE: booleanconst IS_MOCK_MODE: boolean
The debugLog / debugError / debugWarn functions in utils.ts import
from env-spa.ts, and scattered environment comparisons are replaced by
readable constants.
Est. reduction: ~50 lines
Testing: src/lib/env-spa.test.ts (new)
- Validates that
IS_PRODUCTION,IS_STAGE,IS_MOCK_MODEreflect the correspondingprocess.env.NEXT_PUBLIC_*values. - Tests use
jest.replaceProperty(or equivalent) to set env vars per case — no changes to existing test files.
Existing env.test.ts (server-side) is not modified.
Utility Extraction Summary
Section titled “Utility Extraction Summary”| Group | File | Side | Est. Reduction |
|---|---|---|---|
| A — API Route Infrastructure | src/lib/api-route-utils.ts (new) | Server | ~510 lines |
| B — Lookup Parsing | src/lib/ardaClient.ts (refactor) | Client | ~300 lines |
| C — JWT/Token | src/lib/jwt.ts (consolidate) | Mixed | ~30 lines |
| D — Safe Serialization | src/lib/storage.ts (new) | Client | ~130 lines |
| E — Error Handling | src/lib/errors.ts (new) | Mixed | ~80 lines |
| F — Formatters | src/lib/formatters.ts (new) | Client | ~50 lines |
| G — Environment Constants | src/lib/env.ts + src/lib/env-spa.ts | Server + Client | ~50 lines |
| Total | ~1,150 lines |
Success Criteria
Section titled “Success Criteria”- Each extracted utility has its own unit test file with the cases listed in its group above.
- All new tests pass:
npx jest --no-coverage --watchAll=false --forceExit. - No existing test files are modified (except where a group explicitly notes adding cases to an existing suite, e.g., Group B).
- Public API signatures of refactored modules (
ardaClient.ts,jwt.ts,utils.ts) are unchanged — existing consumers compile without edits beyond import paths. - Full local checks pass after each group: lint, typecheck, unit tests, build.
Part 3 — Local Development Tooling
Section titled “Part 3 — Local Development Tooling”Add dev:local workflow for multi-package local development.
tools/dev-local.sh— shell script that:- Builds
ux-prototype(vite, with type declarations) andapi-proxy(tsc) - Starts background watch-mode rebuilds (logs to
scratch/) - Links packages into
node_modulesvianpm link --no-save(does not modifypackage.json) - Starts Next.js dev server in mock mode with
--webpackflag - On exit (Ctrl+C, error, or termination), restores
node_modulesfrom registry vianpm install - Accepts optional port argument (default 3000)
- Builds
package.jsonscript entry —"dev:local": "bash tools/dev-local.sh"- Makefile target —
dev-localtarget that invokesnpm run dev:local - README.md update — document the local development workflow, replacing
the old manual
file:reference approach - CLAUDE.md update — document the local development workflow for agents
Deliverables
Section titled “Deliverables”| # | Deliverable | Location |
|---|---|---|
| 1 | dev-local.sh script | tools/dev-local.sh (new) |
| 2 | dev:local script entry | package.json (modify) |
| 3 | dev-local Makefile target | Makefile (modify) |
| 4 | Updated README.md with local development docs | README.md (modify) |
| 5 | Updated CLAUDE.md with local development docs | CLAUDE.md (modify) |
Success Criteria
Section titled “Success Criteria”tools/dev-local.shis executable and correctly builds, links, runs dev server, and restores on exit.npm run dev:localinvokes the script.npm run dev:local -- 3001runs on custom port.make dev-localinvokes the script.- README.md documents the new workflow, replacing the old
file:approach. - CLAUDE.md documents the workflow for agents.
- Full local checks pass: lint, typecheck, unit tests, build.
PR and Review Workflow
Section titled “PR and Review Workflow”After all three parts are complete and all commits are local:
- Push the branch to origin.
- Create a pull request against
mainwith a summary covering all three parts. - Monitor checks — if any CI check fails, diagnose and fix before requesting review.
- Wait 20 minutes for reviewer comments.
- Present comments to the user as a numbered table with:
- Comment summary
- Recommended action (fix, follow-up ticket, or rationale for no action)
- Act on decisions — commit fixes, create follow-up tickets, or reply with rationale as appropriate.
- Resolve threads once each comment’s action is complete.
Out of Scope
Section titled “Out of Scope”- Image upload implementation (separate project:
image-upload-frontend) - Code restructuring of
src/lib/(tracked in #734) api-proxycode changes or publishingux-prototypecomponent changes
Downstream Dependency
Section titled “Downstream Dependency”This project is a prerequisite for the image-upload-frontend
project (roadmap/completed/item-image-upload/), which lists it as
an entry condition for Phase 3.5 (BFF routes).
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved