# Dashboard Architecture Refactor

Status: **vertical foundation complete for 14 migrated modules; dashboard-wide migration remains incremental**  
Last updated: 2026-08-04

## 1. Safety boundary

This refactor changes dashboard implementation structure only. It must preserve:

- every `/dashboard/...` path and HTTP method;
- middleware order, authentication, authorization, uploads, validation, and CSRF;
- JSON response envelopes and redirect targets;
- current EJS data contracts and approved visual output;
- existing Light, Dark, RTL, mobile, loading, empty, modal, and action behaviour;
- existing API/Auth/Product API contracts and all Mongoose schemas.

No migration, package installation, schema change, global RBAC activation, broad Swagger
rewrite, or UI redesign belongs to this work.

## 2. Inventory snapshot

| Surface | Current size | Main observation |
|---|---:|---|
| Central admin route | 2,322 lines | One route file owns nearly all dashboard registration and middleware ordering. |
| Dashboard controllers | 46 files / 23,460 lines | Most modules mix HTTP, database queries, orchestration, formatting, and render locals. |
| Admin EJS | 492 files | Several accepted modules are already componentised; legacy modules still contain inline scripts/styles. |
| Page JavaScript | 26 files | Behaviour is partly shared and partly module-scoped. |
| Page CSS | 52 files | Frozen premium modules have strong scoped assets; older modules vary. |
| Dashboard-focused tests | 32 files | Existing visual/contract/CSRF gates provide the migration safety net. |

Only Dashboard Home had a dedicated service before this refactor
(`src/services/admin/dashboardHomeService.js`). There were no dashboard repository or
view-model directories.

## 3. Controller/query concentration

The largest query-owning controllers are the highest-risk migration targets:

| Module/controller | Approx. lines | Direct query calls | Risk |
|---|---:|---:|---|
| Auction | 1,591 | 49 | Critical: lifecycle, money, status transitions, actions. |
| Info Site | 1,522 | 84 | Critical: many settings sections and uploads. |
| Home | 1,355 | 93 | High: broad read model; service already exists. |
| Provider | 1,346 | 28 | Critical: status, subscriptions, files, people workflows. |
| Product | 1,322 | 23 | Critical: catalogue, uploads, moderation, variants. |
| Client | 1,185 | 38 | High: people actions, subscriptions, avatar lifecycle. |
| Users | 784 | 26 | High: shared people/resource behaviour. |
| Attributes | 748 | 30 | High: multiple subresources and catalogue relations. |
| Admin | 700 | 26 | High: admin/role cross-cutting behaviour. |
| Reasons | 256 | 14 | Low: compact CRUD, accepted UI, migrated first. |
| Subscriptions | 220 | 6 | Low: read-only registry, migrated first. |

Query counts are a discovery heuristic, not a measure of business complexity.

## 4. Target module anatomy

Each migrated dashboard module is owned by one vertical folder:

```text
src/modules/dashboard/<module>/
  index.js
  <module>.controller.js
  <module>.service.js
  <module>.repository.js
  <module>.viewModel.js
  <module>.constants.js
  <module>.policy.js
  <module>.query.js       # only where query composition is non-trivial
  <module>.validation.js  # only where the module mutates data
```

The dependency direction inside that folder is:

```text
adminRoute
   -> controller          HTTP only: req/res, validation result, response/redirect
      -> service          orchestration and business rules; no req/res
         -> repository    Mongoose query ownership only; no rendering or HTTP
      -> viewModel        safe display shaping and EJS locals; no writes
         -> EJS partials  presentation only
```

### Controller

- Keeps the existing exported handler names required by `adminRoute.js`.
- Reads `req`, calls `checkValidations`, obtains CSRF tokens, and sends/renders.
- Does not import Mongoose models, `ApiFeature`, pagination, or `returnObj` mappers.
- Keeps the existing error and response-envelope conventions.
- May invoke cross-cutting dashboard audit reporting with request context.

### Service

- Receives plain values, not Express request or response objects.
- Owns orchestration, filter construction, pagination decisions, and business flow.
- Delegates all model access to a repository.
- Does not render EJS or decide HTTP status/envelopes.

### Repository

- Is the only new layer that imports Mongoose models.
- Encapsulates model queries, populate plans, write options, and query reuse.
- Does not know about HTTP, i18n, EJS, or UI state.

### ViewModel

- Converts service records into the exact safe shape required by the approved views.
- Owns display-only defaults, filter definitions, titles, and common render locals.
- Does not query or mutate the database.

### EJS

- Existing approved output remains unchanged during architectural extraction.
- Later EJS decomposition is allowed only behind screenshot and render-contract tests.
- Inline legacy CSS/JS is removed only in a dedicated, visually verified module slice.

## 5. Route modularisation strategy

The 2,322-line route file is a real maintenance risk, but middleware-order changes are
also high risk. Route extraction therefore happens after the corresponding controller
slice is green.

The eventual form is a module registrar that receives the existing dependencies:

```js
registerReasonsRoutes(adminRouter, {
  csrfProtection,
  authentication,
  authorization,
  authorizationAjax,
  validators,
  controllers,
});
```

The current safe intermediate state deliberately keeps `adminRoute.js` as the central
aggregator and imports every migrated controller/validator from the module `index.js`.
Route extraction was deferred because it would add middleware-order risk without adding
business value to this slice.

No route path, regex order, middleware order, or handler name may change. Filter routes
must remain before parameterised detail routes. Multipart parsing must remain before
CSRF where required.

## 6. Migration waves

### Wave 1 — foundation

1. Reasons — migrated in the first slice.
2. Subscriptions — migrated in the first slice.
3. Package Features — migrated after the first slice.
4. The central route and frozen EJS/assets remain unchanged while the layer boundaries
   are proven.

### Wave 2 — lower-risk resources

- Packages and Premium Packages — CRUD, feature selection, and subscription guards.
- Socials — CRUD plus the existing validated image lifecycle and preview contract.
- Countries and Cities — visibility, session-scoped country selection, and dependent
  Village/District cleanup.
- Coupons — renewal/closure, subscription usage protection, legacy normalization, and
  expiry-job orchestration.

All Wave 2 controllers now delegate through Controller → Service → Repository →
ViewModel. Routes, middleware order, schemas, EJS, CSS, JavaScript assets, response
envelopes, and public APIs remain unchanged.

### Wave 3 — catalogue

- Wave 3A: Departments, SubDepartments, Attributes, and attribute values — migrated.
- Wave 3B Safety Slice: Products list, filter, details, edit-read model, and the already
  tested moderation orchestration boundary — extracted conservatively.
- Wave 3C Safety Slice: the existing dashboard create/edit mutation orchestration,
  upload rollback, active-order guard, expiry scheduling, and provider notification —
  extracted conservatively. Gold Requests, product reports, and any mutation contract
  that does not already exist in the dashboard remain deferred.
- Preserve all image fallback, variants, moderation, AI-pricing, and catalogue filters.

Wave 3A applies the established Controller → Service → Repository → ViewModel boundary
without changing routes, middleware, validators, schemas, EJS, CSS, JavaScript assets, or
response envelopes. Controller size fell from 296 to 185 lines for Departments, 315 to
182 lines for SubDepartments, and 747 to 388 lines for the combined Attributes/value
controller. Those controllers no longer import models or own Mongoose queries.

Products was not included in Wave 3A. Its dashboard controller combines uploads and file
cleanup, cron scheduling, order guards, moderation, notifications, gold requests,
permission-dependent actions, variants, and several return-object contracts. Wave 3B
therefore uses a Product-specific safety slice rather than treating the controller as a
normal CRUD resource.

#### Wave 3B Products risk inventory and safety boundary

Before Wave 3B the Product dashboard controller was 1,321 lines and exported eighteen
handlers across the main Product catalogue and the legacy Gold Requests sub-resource.
It directly queried Product, Provider, Department, SubDepartment, City, Rate, and Order
records and also coordinated uploads, removed images, expiry cron jobs, active-order
guards, moderation notifications, audit reports, permission-dependent actions, variant
details, and AI-pricing references.

The main moderation read engine now belongs to the vertical Product dashboard module at
`src/modules/dashboard/products/products.moderation.js`. It owns a tested aggregation pipeline,
filter references and statistics, safe list cards, Product Details B2 serialization,
simple/variant separation, media fallback, provider summaries, moderation timeline, and
real AI-pricing range hydration. Rewriting that high-risk engine during an architecture
move would add risk without changing the business result, so the vertical migration
preserves its tested behavior behind the repository boundary. Dashboard media ownership
likewise lives in `src/modules/dashboard/products/products.uploads.js`.

Wave 3B therefore introduces:

- `src/modules/dashboard/products/products.repository.js` as an explicit
  anti-corruption facade around the existing,
  tested query engine plus the unchanged `Product.findById` edit-read query;
- `src/modules/dashboard/products/products.service.js` for list/details/edit-read
  orchestration and behavior-neutral
  delegation of the existing delete/approve/reject/visibility/bulk moderation commands;
- `src/modules/dashboard/products/products.viewModel.js` for pagination,
  permission/action policy, image display paths,
  and the exact existing EJS locals;
- a thin controller path for list, filter, details, and edit GET while preserving all
  paths, methods, middleware/CSRF order, views, assets, and response envelopes.

The controller is 1,212 lines after this slice. The remaining size is intentional:

| Deferred surface | Why it remains in the controller for now |
|---|---|
| Product create mutation | upload ownership, partial-file cleanup, taxonomy checks, and expiry scheduling |
| Product edit mutation | `deletedImages`, append uploads, order guard, cron reschedule, and provider notification |
| Gold Requests | separate list/filter/detail/rate/approve/reject workflow with legacy DTO and notifications |
| Query engine internals | variants, attributes, AI pricing, moderation timeline, and B2 detail contracts are already covered and frozen |
| Product reports | separate dashboard resource; not part of the Product moderation controller safety slice |

This was deliberately reported as a **partial architecture extraction**, not a completed
Products rewrite. Wave 3C continues that boundary without widening the dashboard
contract or importing Provider Product API behavior.

#### Wave 3C Products mutation safety slice

Wave 3C moves only behavior that could be proven equivalent to the existing dashboard
mutation flow:

- the controller still owns request-bound multipart parsing and delegates uploaded-file
  callbacks to the service;
- the repository owns Product create/save operations, taxonomy lookups, provider lookup,
  and the exact existing active embedded-order query;
- the service owns the create/edit sequence, newly uploaded-file rollback on failed
  persistence, expiry-job scheduling, and the existing provider edit notification;
- `deletedImages` continues to remove stored image references while new uploads append;
  existing physical files are not deleted because the legacy dashboard did not delete
  them;
- attributes, variants, AI-pricing fields, moderation DTOs, Product Details B2, and all
  public Product API contracts pass through unchanged;
- main Product delete, bulk delete, approve, reject, and visibility commands continue to
  use the already tested moderation engine extracted in Wave 3B.

The Product controller is 1,018 lines after Wave 3C, down from 1,212 after Wave 3B. The
remaining controller size is intentional and keeps separate legacy workflows out of this
safety slice:

| Deferred surface | Safety decision |
|---|---|
| Dashboard create route | Resolved by the Product Dashboard Contract phase: the pre-existing create EJS, handler, validator, upload helper, and service now have matching GET/POST registrations with the same multipart/CSRF order as edit |
| Replace/remove upload modes | Dashboard validators expose `deletedImages` only; `removedImages` and `imageMode=replace` belong to a different API contract and were not imported |
| Variant/attribute/AI mutations | The dashboard form has no mutation contract for these fields; the service preserves existing stored values instead of inventing inputs |
| Gold Requests | Separate legacy list/filter/detail/rate/approve/reject workflow with its own DTOs and notifications |
| Product Reports | Separate dashboard controller/resource and outside Product mutation scope |

The Product business refactor remains **PARTIAL by contract scope**, while the extracted
Product dashboard slice now has complete vertical ownership. The module owns its
controller, service, repository, view model, constants, policy, query builder, and
validation. The existing create/edit forms have explicit routes and tested contracts,
but this is not a claim that legacy-only fields, variants, AI pricing mutations, or
Product Reports were added or redesigned. Gold Requests remain behavior-compatible and
are orchestrated through the same Product module without changing their routes or DTOs.

#### Product Dashboard contract definition

`docs/PRODUCT_DASHBOARD_CONTRACTS.md` is the authority for dashboard-only Product forms
and actions. The contract inventory proved that `create.ejs`, the shared form,
`createProductPage`, `createProduct`, `validateCreateProduct`, and the upload/service path
already existed while their GET/POST route registrations were missing. The two routes
were restored without adding a catalogue button, field, schema path, or Product API
behavior.

The contract freezes these decisions:

- `deletedImages` is canonical and removes stored references only;
- uploads append and no `imageMode=replace` contract exists;
- `removedImages` is not a dashboard field;
- variants, attributes, and AI-pricing values are preservation-only because no dashboard
  form submits them;
- `region`, `details`, `instructions`, and `expireAt` remain explicitly marked
  legacy/ambiguous because the form and service reference them while the current Product
  schema does not define them. No schema behavior is inferred or changed.

### Wave 4 — people

- Supervisors/Admins, Clients, Providers, Join Requests.
- Preserve status transitions, account safety rules, avatar/file behaviour, and action
  policies. No AccountIdentity/Auth contract changes belong here.

### Wave 5 — operations and finance

- Orders, Auctions, Payments, Settlements, Complaints, Contact, Notifications, Reports.
- Money and lifecycle modules require focused state-transition and CSRF tests before any
  extraction.

### Wave 6 — settings and cross-cutting shell

- Settings Center sections, Permissions, shared shell, search, notifications, and toast
  integrations.
- Frozen Settings UI is decomposed only after every endpoint has a verified save path.

## 7. Vertical module migration proof

The migrated dashboard implementation now lives only in these vertical folders:

- `src/modules/dashboard/reasons/`
- `src/modules/dashboard/subscriptions/`
- `src/modules/dashboard/package-features/`
- `src/modules/dashboard/packages/`
- `src/modules/dashboard/premium-packages/`
- `src/modules/dashboard/socials/`
- `src/modules/dashboard/countries/`
- `src/modules/dashboard/cities/`
- `src/modules/dashboard/coupons/`
- `src/modules/dashboard/departments/`
- `src/modules/dashboard/sub-departments/`
- `src/modules/dashboard/attributes/`
- `src/modules/dashboard/attribute-values/`
- `src/modules/dashboard/products/`

The former horizontal controller, validation, service, repository, and view-model
implementations for those modules no longer remain live. `adminRoute.js` imports module
indices, and `test/dashboardArchitectureRefactor.test.js` enforces folder completeness,
dependency direction, index exports, vertical route imports, absence of duplicate
horizontal implementations, and the OOP presentation boundary.

The second slice applies the same boundary to Packages, Premium Packages, Socials,
Countries, Cities, and Coupons. Controller public exports remain exactly those consumed
by the central router. File upload parsing stays in the Socials/Countries controllers because
the existing helper requires Express request context; persistence and file lifecycle
orchestration live below the HTTP boundary.

The first authenticated browser pass also exposed a stale Subscription query: the
dashboard attempted to populate a removed legacy `provider` path although the current
Subscription schema stores its actor in `user` with `userRef`. The repository now
populates the real relation and the view model adapts it to the unchanged display shape.
Subscriber filtering likewise targets `user` and resolves Client/Provider records without
changing the route or EJS contract.

The Wave 3A slice introduces catalogue-specific repositories, services, and view models
for Departments, SubDepartments, Attributes, and Attribute Values. It preserves the
localized filter semantics, pagination, department/subdepartment relation counts, image
fallbacks, attribute preview values, status mutations, and the existing public controller
exports. `test/dashboardArchitectureWave3Catalog.test.js` verifies those layer boundaries,
route bindings, middleware/CSRF order, and the explicit Product deferral.

The Wave 3B Products safety slice adds `test/dashboardArchitectureWave3BProducts.test.js`.
It verifies the repository boundary, thin read handlers, unchanged route registrations,
safe moderation delegation, exact pass-through of simple/variant/AI detail DTOs, and the
explicit retention of upload/cron/order/Gold high-risk code. Existing Product moderation,
catalogue console, Product Details B2, and Provider Product API tests remain the contract
authority for behavior and rendering.

The Wave 3C mutation safety slice adds
`test/dashboardArchitectureWave3CProductsMutations.test.js`. It verifies the unchanged
multipart/CSRF route chain, allowlisted dashboard mutation payloads, taxonomy-before-upload
ordering, active-order-before-upload guarding, append-only new images, reference-only
`deletedImages`, rollback of newly uploaded files after persistence failure, preservation
of variants/attributes/AI pricing, exact expiry and provider-notification payloads, and
continued delegation to the frozen moderation engine.

Record-backed browser QA covered Product list, simple and variant details, simple and
variant edit forms, invalid edit validation, delete confirmation, and moderation
confirmation across desktop light/dark, compact, tablet, and mobile viewports. It recorded
zero console errors, failed requests, HTTP errors, horizontal overflow, clipped text,
broken images, duplicate IDs, dark-theme leaks, clipped dialogs, or layout shifts.
Destructive mutations were deliberately not executed against the configured data. QA
screenshots and manifests are stored under the ignored path
`public/admin/ui-lab/screenshots/dashboard-architecture-refactor/wave3c-products-mutations/`.

The follow-up contract gate adds `test/dashboardProductsContracts.test.js`. It maps both
create/edit EJS actions to registered routes, freezes the actual field inventories,
asserts one CSRF guard on every Product mutation, executes multipart CSRF rejection and
success harnesses, and prevents future claims for `removedImages`, image replacement,
variants, or AI pricing unless the EJS/validator contract is deliberately changed.

Its record-backed browser gate covered the Product catalogue, the restored create page,
simple and variant details, and simple and variant edit pages in desktop light, desktop
dark, and mobile viewports. Separate passes covered invalid create/edit states plus delete
and moderation dialogs: 54 scenarios in total, with zero console errors, failed requests,
HTTP errors, horizontal overflow, clipped text, broken images, duplicate IDs, clipped
dialogs, or layout shifts. All HTTP mutations and database writes were blocked during
this visual QA. Evidence is stored in the ignored directory
`public/admin/ui-lab/screenshots/dashboard-products-contract-definition/`.

## 8. Per-slice acceptance gate

Every module slice must pass, in this order:

1. `node --check` for every changed JavaScript file.
2. Architecture-boundary tests.
3. Module render/contract tests.
4. CSRF and route-contract tests.
5. Full `npm test` before closing the wave.
6. `git diff --check`.
7. Authenticated browser QA for desktop/light, desktop/dark, and mobile.
8. Populated and empty-state screenshots stored only under ignored QA paths.

If data or authentication blocks browser QA, the slice is reported `PARTIAL` or
`DATA_BLOCKED`; screenshots or results must never be fabricated.

## 9. Explicitly deferred debt

- Splitting `adminRoute.js` before module handlers are isolated.
- Generic base repositories/services that hide module-specific business rules.
- EJS redesign or mass replacement of frozen premium markup.
- Schema/index changes inferred only from controller code.
- Broad query optimisation without profiler/explain evidence.
- Any public API, Auth, Product API, Swagger, or global RBAC change.

These are deliberate safety decisions, not omissions to conceal.

## 10. Final vertical/OOP acceptance (2026-08-04)

The final architecture pass completed vertical ownership for all fourteen modules in
section 7. Every module index exposes its controller, service, repository, view model,
constants, and policy; query/validation files remain module-local only where required.
The central router now consumes those indices while preserving route and middleware
order.

The final OOP audit established:

- controllers and services contain no repeated raw `status`, `type`, or `role`
  comparisons;
- EJS receives presentation flags, CSS modifiers, label keys, and action policies rather
  than comparing domain status values;
- repositories exclusively own migrated model access;
- Product failure reporting uses the shared redacting structured logger rather than
  request-adjacent `console` logging;
- Subscriptions status rendering is mapped by policy and cannot stringify a presentation
  object into the UI;
- Product moderation views, sorts, actions, record state, notification type, and Auction
  status are consumed through the Product module constants contract;
- `input.type === "url"` in the Department form helper and `input.type === 'number'` in
  the Premium Package form helper are HTML-control mechanics, not domain decisions; they
  are the only matching template expressions left by the final anti-magic-string audit.

Products remains **PARTIAL only in business-contract scope**: the extracted dashboard
implementation is a complete vertical module, while undocumented legacy fields,
variant/AI mutations, Product Reports, schemas, and public Product APIs remain outside
this architecture phase. The Product controller is 713 lines after the final ownership
pass; further reduction would require a separately approved business-contract slice.

Final verification:

- focused vertical/Product ownership and contract gate: 88/88 passed;
- full repository test gate: 999/999 passed across 24 suites;
- changed JavaScript syntax: 135 files passed `node --check`;
- `git diff --check`: passed;
- record-backed read-only browser QA: 14 list routes, 84 Light/Dark/System/desktop/
  tablet/mobile scenarios, 42 primary viewport screenshots;
- browser metrics: zero console errors, failed requests, HTTP errors, unexpected
  redirects, overflow, clipped text/modals, broken images, duplicate IDs, raw/unknown
  object leakage, debug panels, table alignment failures, CSRF form failures, dark-theme
  leaks, and modal layout shifts.

The final browser manifest and screenshots are stored below the ignored path
`public/admin/ui-lab/screenshots/dashboard-vertical-modules/`.

## 11. Dirty-worktree accounting

Every path reported by `git status --short` is classified exactly once:

The final machine-checked inventory contains 190 entries: A=165, B=6, C=1, D=3,
E=15, F=0, G=0, and unclassified=0. Generated Postman metadata was restored before
this count; ignored browser evidence therefore does not appear in the 190 entries.

### A — migrated module ownership

- New vertical implementations:
  `src/modules/dashboard/{reasons,subscriptions,package-features,packages,premium-packages,socials,countries,cities,coupons,departments,sub-departments,attributes,attribute-values,products}/**`.
- Removed horizontal controllers:
  `src/controllers/dashboard/{attributesController,cityController,countryController,couponController,departmentsController,packageController,packageFeatureController,premiumPackageController,productController,reasonsController,socialMediaController,subDepartmentsController,subscriptionController}/**`.
- Removed horizontal validators:
  `src/utils/validations/dashboard/{attributes,city,country,coupon,departments,package,packageFeature,premiumPackage,product,reasons,socials,subDepartments}.js`.
- Product-owned dashboard engines moved out of horizontal helpers:
  `src/helpers/products/{dashboardProductMedia,productModerationDashboard}.js` now live as
  `src/modules/dashboard/products/{products.uploads,products.moderation}.js`.
- Module presentation boundaries:
  `views/admin/coupons/{create,edit,includes,show,tds}.ejs`,
  `views/admin/packageFeatures/{edit,show,tds}.ejs`,
  `views/admin/packages/{show,tds}.ejs`,
  `views/admin/premiumPackages/{edit,show,tds}.ejs`,
  `views/admin/products/{show,tds}.ejs`,
  `views/admin/productsGold/{dataTable,show}.ejs`,
  `views/admin/reasons/{partials/formFields,show,tds}.ejs`, and
  `views/admin/subscriptions/{show,tds}.ejs`.

### B — shared dashboard files

- `public/admin/assets/css/components.css` remains shared shell/design-system CSS.
- `src/modules/dashboard/shared/{dashboard.constants,dashboard.query}.js` contains the
  small domain-neutral query/presentation primitives reused across modules.
- `src/helpers/permissions/permissions.js` and
  `src/locales/{ar,en}/permissions.json` remain the intentional shared permission
  catalogue/source translations from the existing route-contract work; they are not
  moved into a feature module.

### C — route aggregator

- `src/routes/dashboard/adminRoute/adminRoute.js` remains central and imports the
  migrated module indices. No route extraction or middleware reordering was attempted.

### D — documentation

- `docs/DASHBOARD_ARCHITECTURE_REFACTOR.md`
- `docs/DASHBOARD_MODULE_GUIDE.md`
- `docs/PRODUCT_DASHBOARD_CONTRACTS.md`

### E — tests

- Updated:
  `test/dashboardCatalogCommerceBilingual.test.js`,
  `test/dashboardCatalogCommerceCluster.test.js`,
  `test/dashboardCatalogCommerceCsrf.test.js`,
  `test/dashboardLayoutShell.test.js`,
  `test/dashboardOperationsContentCluster.test.js`,
  `test/dashboardPackageFeaturesForm.test.js`,
  `test/dashboardProductModeration.test.js`,
  `test/dashboardProductsCatalogConsole.test.js`,
  `test/dashboardReasonsUi.test.js`, and
  `test/dashboardSocialsUi.test.js`.
- Added:
  `test/dashboardArchitectureRefactor.test.js`,
  `test/dashboardArchitectureWave3Catalog.test.js`,
  `test/dashboardArchitectureWave3BProducts.test.js`,
  `test/dashboardArchitectureWave3CProductsMutations.test.js`, and
  `test/dashboardProductsContracts.test.js`.

### F — temporary files

None. No `/tmp` helper was promoted into the repository and no temporary script is in
`git status`.

### G — generated/ignored artifacts

- `postman/.export-meta.json` was changed by the full test/export gate and restored to
  the tracked version after every final run.
- Browser screenshots/manifests under `public/admin/ui-lab/screenshots/` are generated QA
  evidence and are ignored by `.gitignore`; they do not appear in the project diff.
