Optimization (audited across src/, typecheck green): - Crash guards: PurchaseAuction data?.result + stray text-node, AuctionItemDetail length>0, LiveAuction user?.contractorId, KYC params guard - Hoisted render-defined components: Login MinimalInput (keyboard-focus bug), TenderFullDetail Section/InfoBox (subtree remount) - Memoized hot paths: Spreadsheet ColumnHeaderRow/DataRow, list makeStyles, shared infinite-query select (stops per-render flatMap) - Removed hot-path/in-list console logs; SkeletonList 10->1 shimmer UI/theme: themed AppToast, redesigned sidebar drawer, list empty states, KYC WebView cleanup Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
7.9 KiB
ProcureLinc — Architecture Guide (as-built)
This is the as-built companion to ARCHITECTURE_AUDIT.md.
The audit is the blueprint (what was wrong, what to build); this document is the
map of what now exists and the rules for working within it. Read this before
adding a feature or "fixing" something that looks odd — several things that look
like smells are deliberate, documented below under Deferred / do not touch blindly.
1. Layering
Presentation src/Screens/**, src/components/** (UI only)
│
Application src/Services/**, feature hooks (use-cases, orchestration)
│
Domain slices, reducers, pattern configs (business rules, pure)
│
Infrastructure src/core/** (http, storage, crypto, events)
│
Platform react-native, native modules
Dependencies point inward. src/core/** must never import from Screens,
Services, or Redux. It reaches outward only through injection/events
(see §3).
2. The core/ layer — the single source of infrastructure truth
| Module | Responsibility | Rule |
|---|---|---|
core/config/env.ts |
All hosts/base URLs, selected by build type | No hardcoded URLs anywhere else. |
core/http/client.ts |
THE axios instance; request<T>() |
Throws AppError; never returns a sentinel. All new network code uses this. |
core/http/AppError.ts |
Normalized typed error | Catch with isAppError(); branch on .kind. |
core/observability/logger.ts |
The only sanctioned log sink | Never console.* in app code. DEV-only; release-stripped. |
core/observability/redact.ts |
PII/secret masking | Every log payload is redacted; extend SENSITIVE_KEY_PATTERN when adding sensitive fields. |
core/storage/secureStorage.ts |
Keychain-backed persist storage for the auth session | Token is never in plaintext AsyncStorage. |
core/events/appEvents.ts |
Typed cross-layer event bus | How infra signals up without importing the store. |
core/query/queryClient.ts |
Module-singleton React Query client | Never construct a QueryClient in a component. |
core/errors/ErrorBoundary.tsx |
App-root render-error guard | Wraps the tree in App.tsx. |
core/platform/polyfills.ts |
Buffer / TextEncoder | Imported first in index.js; nothing may depend on load order otherwise. |
3. Hard rules (enforced by review; some by CI)
- Networking: use
request()fromcore/http/client. It injects the JWT, handles 401 (emitsauth/unauthorized), times out, and throwsAppError.src/Axios/Axios.ts(apiCall) is a legacy compatibility adapter that preserves the old never-throws{success:false}envelope for un-migrated call sites; its contract is pinned by tests. Do not add newapiCallsites. - Auth token: read only via
selectAuthJwt(state). The transport reads it through a provider registered inRedux/Store.ts— neverimport { store }intocore. - Logout: call
performLogout()fromServices/Auth/session.ts. It clears redux + keychain + the query cache in one place.AuthSessionGuard(mounted inApp.tsx) resets navigation to Login whenever the session ends. - Post-auth routing:
Services/Auth/registrationRouting.tsis the single decision function. Splash and Login both call it. Do not re-implement. - Logging:
logger.<level>(scope, message, data?). Never log key material, plaintext bids, OTPs, or full PII (the redactor is a safety net, not a licence). - Theme/colors: use
useTheme().Constant/Color.tsxis deprecated (see §6). There is one brand orange, sourced fromtheme/colors. - Components: one root —
src/components/**.src/Component/no longer exists. - Navigation: route param shapes live in
src/Navigation/types.ts(RootStackParamList).NavigationStringis theas constroute-name registry. - Forward/Reverse bidding: every behavioural difference lives in
Screens/Bidding/biddingPattern.ts. The screen/card contain zeroif (pattern === ...).
4. Feature anatomy (pattern to copy)
The tender-bidding and registration features show the intended shape — logic is extracted out of the screen into pure, testable units:
Screens/Tender/tenderBidding/
TenderBidding.tsx presentation + state only
envelopeTypes.ts types
envelopeCrypto.ts AES/RSA (never logs key material)
envelopePayload.ts pure payload build + validation ← unit-tested
envelopeSubmission.ts API calls
components/* memoized, module-scope sub-components
Screens/Ragistration/
Ragistration.tsx orchestration + render
registrationState.ts typed useReducer (was ~20 useStates) ← unit-tested
registrationValidation.ts pure per-stage validation ← unit-tested
When you build a feature: keep the screen thin; put decisions in pure functions; test the pure functions.
5. Testing & CI
npm test(jest). 75 tests across 11 suites, concentrated on the money/crypto/auth/storage paths:envelopePayload,biddingPattern,registrationState/registrationValidation,registrationRouting,Tokenslice,apiCallenvelope contract,AppError,redact,appEvents, and thesecureStoragekeychain-migration path.- CI (
.github/workflows/ci.yml) runs install → typecheck → lint → test, all blocking.tsc --noEmitis green;eslint . --quiet(errors) is green. Style warnings still exist and are being burned down; tighten to--max-warnings=0once clean. - New pure logic should ship with tests. UI can follow with RNTL later.
6. Deferred / do not touch blindly
These look like leftovers but were left deliberately. Read before "fixing".
Constant/Color.tsxis deprecated but still imported by ~20 files for neutral grays. Its brand tokens are now sourced from the theme, but its neutral values differ fromtheme/colorsin several places. A blind find/replace touseTheme()will change appearance — migrate screen-by-screen with visual review.EnvelopeCExcelPreview.tsxstill uses raw axios (responseType: 'arraybuffer') andreact-native-fswith'ascii'write encoding. The RNFS →react-native-blob-utildedupe and the axios →core/httpmigration are deferred because this sealed-bid document flow can't be runtime-verified here and the encodings aren't 1:1. Needs a device.- Sealed-bid crypto is AES-ECB with the symmetric key echoed to the server
(
randomKeyGenerated). This is a backend-contract weakness (audit §11); fixing it requires a coordinated backend change, not a client edit. ResetPasswordsends the new password as a URL query param. Migrated tocore/http(so it's redacted in logs) but the query-param placement is a backend contract — see theTODO(backend)at the call site.PurchaseTenderTile"details" tap (inPurchaseTenderAllList) now navigates to the registeredLiveTenderDetailroute, passing the flat tenderitemthat screen reads fromroute.params— matchingLiveTenderTile's working pattern. It previously targeted an unregistered'TenderDetail'route (a latent bug that no-op'd with a dev warning).
7. Open work (gated on device or backend/ops)
- Device/emulator needed: full Auth/App two-stack navigator split
(
AuthSessionGuard+registrationRoutingare the groundwork); release minify/shrink + App Bundle + ABI splits; verifying the Envelope C flow. - Backend/ops needed: HTTPS for
eproc-backend.lincpay(then remove its cleartext exemption innetwork_security_config.xmland add pinning); rotate the previously-committed keystore + Sonar token and purge git history. - Incremental: finish
Constant/Color→useTheme; per-screen typed navigation adoption (useNavigation<NativeStackNavigationProp<RootStackParamList>>); broaden accessibility labels; i18n scaffolding.