UI Implementation Analysis: Direct Email Send (arda-frontend-app)
Purpose
Section titled “Purpose”This document analyzes the current arda-frontend-app structure around the
Email Order composer and proposes how to update it to the redesigned
direct-send experience (prototyped in email-order-ui.md,
specified in design.md), holding the work to the repository’s
own conventions and the Arda front-end / design skills: component design,
styling/design-system, TypeScript, unit + e2e testing, file-size limits, and DRY.
It complements design.md (the cross-system design): where that maps the
feature onto the backend contract, this maps it onto the real front-end
codebase — concrete files, the gap from today’s code, the target module
decomposition, and the test plan.
Findings below cite the phase-6 worktree (
projects/email-integration-worktrees/phase-6/arda-frontend-app) atfile:line. They reflect the code as read on 2026-06-26.
1. Current State
Section titled “1. Current State”1.1 The Email Order subsystem
Section titled “1.1 The Email Order subsystem”The composer is src/components/EmailPanel.tsx (458 lines). It is opened
from three places in the Order Queue, always with orderMethod === 'Email'
items:
OrderQueueGroupedView.tsx:475–478— per-card “Email” action (single item).orderQueueHandlers.ts:138–142— bulk “Order All / Complete All” on an Email group.OrderQueuePanels.tsx:403–405— the “Missing information” modal path.
It is mounted in OrderQueuePanels.tsx:296–309 with:
<EmailPanel isOpen={isEmailPanelOpen} onClose={…} items={selectedItemsForEmail} // OrderItem[] onSendEmail={handleSendEmail} // exists in handlers, NOT wired to a button onCopyToClipboard={handleCopyToClipboard} userContext={userContext || undefined} // from JWTProvider/>Data available at the call site (OrderItem, orderQueueSlice.ts:9–50):
id (kanban card eId), name, quantity, orderMethod, status, supplier
(a name, not an email), taxable?, sku?, unitPrice?, unitCurrency?,
notes? / orderingNotes?, itemEntityId?.
What EmailPanel.tsx does today (read in full):
- Builds an ASCII plain-text table (
buildPlainTextWithTable, lines 48–103) and renders the same content as HTML with inlinestyle={{…}}for the items table (lines 256–394). - Copy writes both
text/html(frombodyTextRef.current.innerHTML, line 124) andtext/plainto the clipboard; on success callsonCopyToClipboard. - Footer is Cancel + Copy to clipboard only — there is no Send button,
even though
onSendEmailis a prop andhandleSendEmailexists.
The accept-after-action flow (the part we must preserve):
handleCopyToClipboard(orderQueueHandlers.ts:327–372): on copy, POSTs/api/arda/kanban/kanban-card/{id}/event/acceptper item, thenmarkItemStalerefreshKanbanData+ closes the panel.
handleSendEmail(orderQueueHandlers.ts:193–323): POSTs the send-order route, and on success runs the same accept/close flow. It is implemented but unreachable because no Send control invokes it.
1.2 The integration layer
Section titled “1.2 The integration layer”- BFF stub:
src/app/api/email/send-order/route.tsverifies the JWT (processJWTForArda), composes subject + HTML server-side ("{tenantCompanyName} Order -- {date}"), logs to console (TODO: integrate email service), and returns{ ok, data: { subject, htmlContent, recipientEmail } }. Its test (route.test.ts, 106 ln) covers 401 / success / subject / delivery / greeting / 500 — but never a real send. - Canonical proxy pattern (
src/app/api/arda/items/route.ts:1–61,business-affiliate/route.ts):processJWTForArda(request)→extractUserContext→ forward to${env.BASE_URL}/…withAuthorization: Bearer ${env.ARDA_API_KEY},X-Request-ID(generateRequestId()),X-Author,X-Tenant-Id,X-oidc-subject,cache: 'no-store', thenparseUpstreamResponse+forwardAsNextResponse; wrapped inwithCors. Helpers live insrc/lib/api-route-utils.ts. Guardrail: BFF routes must never log header objects (eslintno-restricted-syntax, PDEV-478,eslint.config.mjs:36–53). - Client layer (
src/lib/ardaClient.ts): functions call/api/…viafetchwithgetBffAuthHeaders()(src/lib/auth-headers.ts:25–98, which returns bothAuthorizationandX-ID-Tokenand proactively refreshes within 5 min of expiry), then normalize viahandleApiResponse(401-with-”jwt” ⇒handleAuthError). Never call the ARDA backend directly. - Caching precedent:
itemsSlice+itemThunkscache per-tenant data in Redux Toolkit (+ redux-persist). A capability flag fits the same shape. - MSW:
src/mocks/handlers/email.tsalready stubsPOST /api/email/send-orderand aconfig-statusGET; registered insrc/mocks/handlers/index.ts.
1.3 Conventions and quality bars
Section titled “1.3 Conventions and quality bars”| Dimension | Rule | Source |
|---|---|---|
| TypeScript | strict: true; no-explicit-any (error; only table/* exempt for AG Grid); no unused vars; @/ path alias | tsconfig.json, eslint.config.mjs:6–9 |
| Styling | Semantic tokens (--base-primary #fc5a29, bg-primary, text-foreground, border-border); cn() (src/lib/utils.ts:4–6); no hardcoded hex, no raw inline style={{}}; a genuinely new style is escalated to Sebrand Warren (@nail60), never hardcoded | globals.css:61–193 |
| Design system | Import from @arda-cards/design-system/canary (v6) — Button, IconButton, DropdownMenu, TypeaheadInput, ReadOnlyField; don’t hand-roll buttons | grep; jest.config.js:62–63 |
| Component design | follow the ux-prototype docs component rules (kebab-case files; lifecycle-phased prop interfaces Static/Init/Runtime; controlled/uncontrolled; useId + aria-*) | ui-component skill; ux-prototype docs/ |
| Code tiers | every file is exactly one of SPA (browser), BFF (server), or shared (both); shared code must be import-safe for both (no window, no server env); enforce with eslint import boundaries | this doc §3.0 |
| File size | Target 300–500 ln production; co-locate feature-specific hooks/utils with their component; decompose early | conventions; oversized examples (ItemFormPanel 2578) |
| Unit tests | Jest + RTL + user-event; renderWithAll() (src/test-utils/render-with-providers.tsx); mock-factories.ts; MSW; scenario mocks for the 8 branch categories; no render-only tests, no conditional guards, getBy* over queryBy* | mocking-patterns skill; jest.config.js:34–40 |
| Coverage | lines 84 / statements 84 / functions 81 / branches 72 | jest.config.js:34–40 |
| E2E | Playwright page objects (e2e/pages/*.page.ts); mock mode default; auth.setup.ts storageState; Istanbul coverage fixture (e2e/fixtures/base.ts); multi-browser | playwright.config.ts |
| State | useAuth() from src/store/hooks/useAuth.ts for new code (not legacy AuthContext) | FE CLAUDE.md |
| Pre-push | npm run lint, tsc --noEmit, npm test, make ci-replicate; PR-body changelog | FE CLAUDE.md |
2. Gap Analysis — current EmailPanel.tsx vs the new design + best practices
Section titled “2. Gap Analysis — current EmailPanel.tsx vs the new design + best practices”| # | Gap | Today | Required |
|---|---|---|---|
| G1 | Monolith | one 458-line file, all concerns inline | decompose to an email-order-panel/ module + co-located hooks/utils (§3) |
| G2 | Hardcoded / raw styles | #fc5a29, #e5e5e5, #e2e8f0, inline style={{}} table | semantic tokens + cn() + canary Button/IconButton |
| G3 | No design-system usage | raw <button> elements | canary components |
| G4 | No Send action | Copy-only footer; onSendEmail unused | Send (primary, ⌘↵) gated by the toggle; Copy secondary |
| G5 | No capability toggle | always the same form | full vs restricted form from cached config-status (DQ-001/002) |
| G6 | Static, non-editable body | read-only render | editable recipients / Subject / greeting / qty / price / note / sign-off |
| G7 | Recipient gap | only supplier name | To/Cc recipients; no contact email at call site (see OQ-1) |
| G8 | Subject | composed server-side in the stub | editable field, default Order for {supplier} — {MMM d, yyyy} (DQ-003) |
| G9 | Body HTML provenance | bodyTextRef.innerHTML (class-based, brittle for email) | deterministic inline-styled HTML + plain text (DQ-005) |
| G10 | No validation | none | ≥1 To, RFC addresses, non-empty subject, escape user content (DQ-006) |
| G11 | No idempotency / tenant header from session | n/a (stub) | BFF injects X-Tenant-Id + Idempotency-Key (DQ-007) |
| G12 | i18n / hygiene | Spanish comments (lines 22, 255) | en-US comments; remove dead deliveryAddress wiring or wire it (OQ-5) |
| G13 | No server-side body protection | backend forwards htmlBody verbatim; Postmark doesn’t sanitize | shared BFF allow-list-sanitizes + rejects if stripped (DQ-010); backend hardening = PDEV-976 |
3. Proposed Target Architecture
Section titled “3. Proposed Target Architecture”3.0 Implementation Conventions
Section titled “3.0 Implementation Conventions”These rules govern every artifact this feature adds or refactors. They derive from the Arda front-end / design skills and the repository conventions in §1.3.
Grow the panel into a module. EmailPanel.tsx will gain complexity, so it
becomes a directory src/components/email-order-panel/ holding the main
component, its subcomponents, and co-located feature-specific helpers/hooks/types.
Reuse before building. For every new element — component, hook, type, effect, util — follow this decision flow:
- Search
ux-prototype/canary for a reusable element; if found, import it. - Search
arda-frontend-app; reuse, extending/modifying if close. If the result is worth generalizing, refactor it into a reusable and add it to the promotion ticket (below). - If nothing fits, decide general-use vs feature-specific:
- General-use → create as a standalone in
src/components/<name>/, import it into the panel, and add it to the promotion ticket. - Feature-specific → create inside
email-order-panel/— a single file for simple elements, or a subdirectory (component + styles + tests) for complex ones.
- General-use → create as a standalone in
- The same procedure applies to hooks, types, effects, and utilities, not just visual components.
Follow the ux-prototype component rules (docs/ in that repo, mirrored by
the ui-component skill): naming, lifecycle-phased configurability, file
structure, styling, and testing.
No raw styles. Never use inline style={{}} or hardcoded hex. Use semantic
tokens or Tailwind classes via cn(). A genuinely new style is escalated to
Sebrand Warren (@nail60) for the design system — not invented inline.
Three-tier separation. Every file is exactly one of:
<<SPA>>— runs in the browser (React components, hooks, the Redux slice, theardaClientwrappers).<<BFF>>— runs on the server (theapi/.../route.tshandlers and the backend-access proxy that holdsARDA_API_KEY).<<shared>>— imported by both (pure constants, types, validation). Shared code must be import-safe for both runtimes (nowindow/DOM, no server-only env). Recommend enforcing the boundaries with eslint import rules so an<<SPA>>module can never import a<<BFF>>one and vice versa.
Co-locate by cohesion. React imposes no restriction on where hooks, effects,
utilities, state, or types live — they are ordinary modules. So feature-specific
auxiliary code lives next to the component that needs it inside
email-order-panel/; only genuinely shared or general-use code is hoisted to
src/lib/, src/types/, or src/components/.
Promotion ticket. When implementation surfaces components (or other elements)
worth promoting to ux-prototype, record them in a single Linear ticket that
enumerates each candidate with its intent, design, and implementation notes, and
assign it to sebrand@arda.cards. Do not promote piecemeal.
3.1 Module map
Section titled “3.1 Module map”The feature decomposes into modules grouped by execution tier. Each unit stays well under the 500-line target and is independently testable.
3.2 SPA — src/components/email-order-panel/ and friends
Section titled “3.2 SPA — src/components/email-order-panel/ and friends”email-order-panel.tsx<<SPA>>— orchestrator: slide-over chrome, the full-vs-restricted layout switch (from the cached toggle), and the footer (Cancel/Revert all(only when dirty) /Copy to clipboard/Send) built from canaryButton/IconButton/SplitButton. Send is a canarySplitButton(like the Items page “Add Item”): default action sends HTML, the caret menu offers “Send as HTML” / “Send as plain text” (DQ-011) — it forwardsloading/disabled/tooltipper the arda-design SplitButton rule. Copy writes bothtext/htmlandtext/plain(unchanged). Restricted mode hides the addresses + Send and makes Copy primary (mirrors the prototype’sdirectSendprop).email-body-preview.tsx<<SPA>>(feature-specific) — the on-screen body card (greeting → items table → note → sign-off → “Powered by Arda”), styled with Tailwind tokens. The email HTML payload is generated separately (§3.3), not scraped from the DOM.use-email-composer.ts<<SPA>>(co-located) — composer state (recipientsto/cc/from, subject, greeting, intro, quantities, prices, note, sign-off),isDirty,revertAll, single-step addressundo.use-email-send.ts<<SPA>>(co-located) — orchestrates Send for the chosen format (HTML ⇒htmlBody+textBodyalternate; plain text ⇒textBodyonly):validate→compose→ardaClient.sendEmailOrder(...)→ on success run the existing accept-after-send flow (event/acceptper card,markItemStale,refreshKanbanData, close) → toasts. OneIdempotency-Keyper Send attempt, reused on retry.compose-email-html.ts<<SPA>>(co-located) — builds the inline-styledhtmlBody+ plaintextBodyfrom composer state, HTML-escaping every user field (DQ-005/006). Replaces the brittleinnerHTMLcapture.recipient-chips/andeditable-text/— Gmail-style chip field and the click/✎-to-edit text element. No canary equivalent today (per §3.0 step 1–2), so they are general-use candidates: created standalone insrc/components/, imported by the panel, and added to the promotion ticket (OQ-4). If review judges them feature-specific instead, they move insideemail-order-panel/.store/emailConfigSlice+ thunk<<SPA>>—fetchEmailConfigStatus(tenantId)dispatched after sign-in (inauthThunks) and on tenant switch; caches{ directSendEnabled, configurationEId, senderAddress }; selectors feed the panel. Session-scoped unless persistence is desired (OQ-8).
3.3 Shared — src/lib/email/, src/types/
Section titled “3.3 Shared — src/lib/email/, src/types/”email-constants.ts<<shared>>—PROCUREMENT_EMAIL_SLUG_TOKEN = 'procurement'and the subject/greeting/sign-off default builders. Shared because theconfig-statusroute (BFF) and the composer (SPA) both consume the token.validate-email-order.ts<<shared>>—≥1 To, RFC-valid To/Cc/Reply-To, non-empty subject, and no CR/LF or control characters in addresses, subject, or Reply-To (header/field-injection defense); structured errors for a blocking toast. Shared so the BFF can re-validate as defense-in-depth (OQ-11).types/email.ts<<shared>>—EmailJobInput,EmailRecipients, configuration-query result,EmailConfigStatus; mapped to lean UI types viaardaMappers.ts. (Pure types — no runtime imports.)
3.4 BFF — src/app/api/arda/email/ + the backend proxy
Section titled “3.4 BFF — src/app/api/arda/email/ + the backend proxy”Per §3.0 (L269 guideline), backend access is isolated in a reusable proxy so the route handlers stay thin and DRY:
lib/arda/email-proxy.ts<<BFF>>— typed functionsqueryEmailConfigurations(ctx)andsubmitEmailJob(ctx, input)that own the${env.BASE_URL}URLs, theAuthorization/X-Tenant-Id/X-Author/X-oidc-subject/Idempotency-Keyheaders,cache: 'no-store', and response parsing. HoldsARDA_API_KEY; never imported by SPA code.config-status/route.ts(GET)<<BFF>>— JWT-verify, callqueryEmailConfigurations, select a config that is Operational withidentity.sendingDomainSlugcontaining the token, return{ directSendEnabled, configurationEId, senderAddress }. (The backend exposes no dedicated config-status endpoint — see OQ-3.)send/route.ts(POST)<<BFF>>— JWT-verify, allow-list-sanitizehtmlBody(viasanitize-email-html.ts) and reject400if anything was stripped (DQ-010), then buildEmailJobInput(configurationEId,recipients {to, cc, bcc: []},subject,htmlBody,textBody,replyToEmail,attachments: []), callsubmitEmailJob, map the outcome, and never log recipients or body (extends the no-header-logging guardrail, PDEV-478). Replaces the/api/email/send-orderstub (OQ-2).sanitize-email-html.ts<<BFF>>— server-side allow-list sanitizer (safe formatting tags only; noscript/iframe/style/event handlers), e.g. viasanitize-html. Restrict<a href>tohttp/https/mailto(blockjavascript:/data:); disallow remote<img>(tracking pixels / SSRF). Shared by every client of the route (incl. the upcoming PO direct send); the backend equivalent (source-of-truth) is tracked by PDEV-976.
3.5 Mocks
Section titled “3.5 Mocks”src/mocks/handlers/email.ts: extend the existing handlers to the two routes, returning the new shapes for mock-mode dev + e2e.
4. Best-Practices Conformance Plan
Section titled “4. Best-Practices Conformance Plan”Hard rules for this work (reinforced by review):
- DRY — one source of truth for the
procurementtoken, the defaults, the compose/validate logic (Copy and Send both consumecompose-email-html.ts), and backend access (both routes go throughemail-proxy.ts). - No raw styles — no inline
style={{}}, no hardcoded hex; only semantic tokens / Tailwind viacn(); new styles escalate to Sebrand Warren (@nail60). - Canary first — use
@arda-cards/design-system/canarycomponents wherever one fits; build locally only after the §3.0 reuse search fails. - Placement — feature-specific elements live in
email-order-panel/; general-use candidates live insrc/components/(and go on the promotion ticket). - Tier separation — SPA / BFF / shared kept distinct, enforced with eslint import boundaries; shared modules import-safe for both runtimes.
- Co-location — auxiliary code (hooks, utils, types) sits next to the component that needs it when feature-specific.
- TypeScript —
strict, noany, lifecycle-phased prop interfaces per theui-componentskill. - File size — every file ≤ ~500 ln; decompose early.
- Accessibility —
useIdfor field ids,aria-invalid/aria-describedbyon invalid fields, ≥44px touch targets, focus management on the slide-over. - Cross-Universe rule — recipients/config referenced by id/value only; no shared FKs or transactions across services.
- Body injection — the shared BFF send route allow-list-sanitizes
htmlBodyand rejects (400) if anything is stripped (DQ-010); the SPA still escapes user content at compose time. Backend hardening is tracked by PDEV-976. - Other security controls — link-scheme allow-listing + no remote images in
the sanitizer, CR/LF & control-char validation of addresses/subject/Reply-To,
no recipient/body logging, and bearer-token (non-cookie) auth. See
design.md§Security; the product/abuse decisions are ingoal.md.
5. Testing Strategy
Section titled “5. Testing Strategy”5.1 Unit (Jest + RTL + user-event, renderWithAll, MSW, mock-factories)
Section titled “5.1 Unit (Jest + RTL + user-event, renderWithAll, MSW, mock-factories)”Per mocking-patterns: scenario-specific mocks across the 8 branch categories;
no render-only tests; no conditional guards; getBy* over queryBy*; meet
84/84/81/72 coverage. Tests are co-located with their target.
| Target | Key scenarios |
|---|---|
validate-email-order | empty To (error), malformed To/Cc/Reply-To, empty subject, CR/LF or control chars in any field (error), valid envelope |
compose-email-html | escapes < > & " in every user field; note included only when non-empty; html + text parity |
use-email-composer | edit → isDirty; revertAll; address undo |
emailConfig thunk | Operational + slug ⇒ enabled; Draft/Provisioning/Failed ⇒ disabled; query failure ⇒ disabled |
email-order-panel | full vs restricted layout; Send hidden when restricted; Copy primary when restricted; Subject default |
use-email-send | happy path (send, then accept per card, toast, close); backend error ⇒ error toast, no accept; retry reuses key |
sanitize-email-html | strips <script>/<iframe>/<style>/onerror=/javascript:/data: hrefs and remote <img>; preserves table/p/a/strong/em/br/ul/li unchanged |
email-proxy + send/route | builds EmailJobInput; headers present (X-Tenant-Id, Idempotency-Key); rejects 400 when body sanitization strips content (DQ-010); 401 / upstream-error mapping; no header logging |
config-status/route | maps query result to toggle; query failure ⇒ not-enabled |
5.2 E2E (Playwright, mock mode, page object)
Section titled “5.2 E2E (Playwright, mock mode, page object)”Add e2e/pages/email-order-panel.page.ts and a spec:
- Happy path — open the panel on an Email group → (toggle enabled via MSW) → edit a field + Subject → Send → success toast → panel closes → card moves to accepted/requested.
- Restricted — MSW returns
directSendEnabled:false→ no Send; Copy works and accepts. - Validation — clear To → Send → blocking error toast; no network call.
Reuse auth.setup.ts storageState and the Istanbul coverage fixture.
6. Proposed Implementation Increments
Section titled “6. Proposed Implementation Increments”- Foundation —
email-constants.ts+types/email.ts(shared); MSW handlers for both routes. - BFF + toggle —
email-proxy.ts;config-status+sendroutes;emailConfigSlice/thunk + dispatch on sign-in/tenant-switch + selectors. - Pure utils (TDD) —
compose-email-html.ts+validate-email-order.tswith unit tests first. - Decompose UI —
email-order-panel/(orchestrator +email-body-preview), run the §3.0 reuse search forrecipient-chips/editable-text, apply semantic tokens + canary, add the co-locateduse-email-composer. - Send path —
use-email-send; wire the toggle to full/restricted; retire the/api/email/send-orderstub. - E2E — page object + specs.
- Cleanup & handoff — remove Spanish comments and dead
deliveryAddresswiring (or wire it); confirm file sizes; compile the ux-prototype promotion ticket (assignedsebrand@arda.cards) listing any promotion candidates; runmake ci-replicate.
Each increment is independently shippable behind the toggle (restricted mode is the current copy-only behavior, so partial rollout is safe).
7. Assumptions & Open Questions
Section titled “7. Assumptions & Open Questions”These are decisions made to keep moving; please confirm or revise.
- OQ-1 — Recipient email source. No supplier contact email exists at the
EmailPanel call site today (
supplieris a name).handleSendEmailreads richeroriginalApiData[...].payload.itemDetails.primarySupply, so the supplier Business Affiliate contact email is plausibly reachable there or via thereference-data/business-affiliatemodule. Assumption: the To field starts empty / user-entered for v1; prefilling is a follow-up once that data is wired. Is empty-To-then-type acceptable for the first release? - OQ-2 — BFF route namespace. Assumption: add the new proxy routes under
src/app/api/arda/email/{config-status,send}(house proxy convention) and retire the non-proxysrc/app/api/email/send-orderstub. OK to relocate, or keep the/api/email/*path? - OQ-3 — config-status backend call. The backend exposes no dedicated
config-status endpoint; the route will
POST /v1/shop-access/email/configuration/queryand apply the Operational + slug rule (matchesdesign.md). Confirm this is the intended detection mechanism (vs. a future dedicated endpoint). - OQ-4 — New shared components.
recipient-chipsandeditable-texthave no canary equivalent. Assumption: build them as general-use candidates insrc/components/now and list them on the promotion ticket. Or build them directly inux-prototypefirst (theui-componentskill’s preference)? - OQ-5 — Note vs Deliver-to. Assumption: the editable Note (from the
prototype) is in scope; the existing-but-unused
deliveryAddressprop is left out of scope unless wired. Keep deliver-to out for v1? - OQ-6 — Subject default. Assumption:
Order for {supplier} — {MMM d, yyyy}(en-US), editable. The current stub uses{tenantCompanyName} Order — {date}. Should the default include the tenant company name instead of / in addition to the supplier? - OQ-7 — From → Reply-To. Per the prototype decision, the editable From maps
to
replyToEmail; the actual From is the config sender, and the field label stays “From”. Confirm this carries into production. - OQ-8 — Toggle cache durability. Assumption: cache the toggle in Redux
session-scoped (re-fetched on sign-in / tenant switch), not redux-persist.
Persist across sessions like
items? - OQ-9 — Accept-after-send semantics. Assumption: reuse the existing
handleSendEmailaccept flow unchanged (accept each card on success). Confirm send failures must leave cards un-accepted (no partial state). - OQ-10 — File naming convention. The ux-prototype rule is kebab-case, but
arda-frontend-app’s existing components are PascalCase (
EmailPanel.tsx). Assumption: use kebab-case for the newemail-order-panel/module and the promotion-candidate components (per the ux-prototype rules), accepting a local mismatch. Confirm, or match the local PascalCase for files that stay in arda-frontend-app? - OQ-11 — Where validation runs. Assumption:
validate-email-orderis shared so the BFF re-validates as defense-in-depth. Or keep it SPA-only (co-located) and rely on backend validation server-side?
References
Section titled “References”- Goal: Direct Email Sending for Email Orders
- Design: Direct Email Send Integration
- Decision Log
- Email Order UI — Direct Send Design (prototype)
- Email module API reference
- Skills applied:
ui-component(React component design),mocking-patterns(frontend testing),react-best-practices(Vercel performance rules),arda-design(tokens, canary, accessibility).
Copyright: (c) Arda Systems 2025-2026, All rights reserved
Copyright: © Arda Systems 2025-2026, All rights reserved