diff --git a/tools/v1/team/customer-support-macro-tool/docs/ARCHITECTURE.md b/tools/v1/team/customer-support-macro-tool/docs/ARCHITECTURE.md new file mode 100644 index 00000000..78a2d0d6 --- /dev/null +++ b/tools/v1/team/customer-support-macro-tool/docs/ARCHITECTURE.md @@ -0,0 +1,212 @@ +# Customer Support Macro Tool - Architecture Contract + +This document defines the module boundaries, dependency rules, data flow, and +future integration constraints for the Customer Support Macro Tool. The tool is +a self-contained V1 team mini-product and must remain isolated in +`tools/v1/team/customer-support-macro-tool/`. + +## Ownership Boundary + +All source, fixtures, tests, and docs for this issue stay inside: + +```text +tools/v1/team/customer-support-macro-tool/ +``` + +This tool does not modify or depend on the main application shell, dashboard +layout, routing, inbox architecture, mail rendering engine, compose-send flow, +authentication, wallet core, Stellar core, database schema, notification +delivery, provider SDKs, or shared design system. + +## Folder Structure + +```text +tools/v1/team/customer-support-macro-tool/ +|-- README.md +|-- specs.md +|-- vitest.config.ts +|-- docs/ +| |-- ARCHITECTURE.md +| |-- DATA_OWNERSHIP.md +| |-- REVIEW_NOTES.md +| `-- SETUP.md +|-- fixtures/ +| `-- macros.fixture.ts +|-- hooks/ +| `-- useMacros.ts +|-- services/ +| |-- macro.service.ts +| `-- storage.service.ts +`-- tests/ + |-- TEST_PLAN.md + |-- architecture-contract.test.mjs + |-- macro.helpers.test.ts + |-- macro.service.test.ts + `-- storage.service.test.ts +``` + +A future `components/` module may be added by a UI-specific issue, but this +architecture issue does not mount the tool in the app shell. + +## Module Boundaries + +### Macro Service: `services/macro.service.ts` + +The macro service owns pure business logic: + +- Macro creation, updates, deletion, and usage count updates. +- Search, category filtering, tag filtering, favorite filtering, and sorting. +- Variable extraction and interpolation. +- Input validation for title, body, and category. + +Service rules: + +- No React imports. +- No network calls. +- No persistence writes. +- No production inbox reads. +- No mutation of input arrays or fixture modules. +- No imports from `src/`, app routes, inbox modules, compose modules, auth, + wallet, Stellar, database, notification, provider, or sibling tool modules. + +### Storage Service: `services/storage.service.ts` + +The storage service owns the folder-local persistence adapter boundary: + +- `StorageAdapter` defines `getItem`, `setItem`, and `removeItem`. +- `localStorageAdapter` is a browser-local adapter. +- `createInMemoryAdapter()` is for tests and non-browser review. +- `loadMacros`, `saveMacros`, and `clearMacros` serialize macro arrays through + the adapter. + +Storage rules: + +- The current V1 storage is local browser storage or in-memory test storage. +- No server database, queue, API route, or shared app store is introduced here. +- Future server-backed storage must use a separate integration issue. + +### Hook Layer: `hooks/useMacros.ts` + +The hook owns local React state orchestration: + +- Loads macros from an explicit storage adapter. +- Seeds from local fixtures when storage is empty. +- Persists state changes through the selected adapter. +- Exposes CRUD, usage, search, sort, and validation actions. + +Hook rules: + +- The hook may import React and folder-local services only. +- The hook must not import app stores, routes, inbox state, auth state, or + compose-window internals. +- Integration must happen through adapter options or a future wrapper, not by + changing the hook to depend on app globals. + +### Fixture Layer: `fixtures/` + +Fixtures are local examples for tests and development. They must not contain +real customers, real ticket ids, real order ids, secrets, API keys, production +email content, or private support data. + +### Test Layer: `tests/` + +Tests verify: + +- Macro service CRUD, search, sorting, validation, and interpolation behavior. +- Storage adapter load, save, clear, and round-trip behavior. +- Architecture contract, required docs, import isolation, and forbidden + integration imports. + +### Documentation Layer: `docs/` + +Docs own reviewer guidance, architecture boundaries, data ownership rules, and +setup notes. Future integration plans belong in docs until a separate issue +explicitly allows code wiring. + +## Data Flow + +```text +Fixture seed or storage adapter data + -> useMacros() local state + -> macro.service.ts pure operations + -> updated macro array + -> storage.service.ts adapter persistence + -> UI or caller receives derived macro state +``` + +Macro interpolation has a separate pure flow: + +```text +Macro body + variable map + -> interpolateMacro() + -> rendered response text + -> caller decides whether to insert it into a compose surface +``` + +The current flow does not send mail, mutate inbox threads, create tickets, +write server records, notify users, call providers, or touch wallet/Stellar +state. + +## Dependency Rules + +Allowed: + +- Relative imports that resolve inside `tools/v1/team/customer-support-macro-tool/`. +- React imports in `hooks/`. +- Vitest imports in tests. +- Node built-ins in contract tests. +- TypeScript type-only imports inside this folder. + +Not allowed: + +- `src/` imports or aliases such as `@/`. +- Imports from sibling tools or parent directories that escape this folder. +- Main app routing, dashboard, inbox, compose, auth, wallet, Stellar, database, + notification, provider, or design-system modules. +- New npm dependencies for this architecture issue. +- Live network calls, secrets, production credentials, provider SDKs, webhooks, + or background jobs. + +## Future Contributor Contract + +Contributors may: + +- Add local tests, fixtures, and docs. +- Refine macro validation, interpolation, search, and sorting rules with + matching tests. +- Add future `components/` files inside this folder when scoped to a UI issue. +- Add adapter implementations when they remain explicit and folder-local. + +Contributors may not: + +- Wire this tool into application routes, navigation, inbox views, or compose + surfaces. +- Persist macros to a server database or shared app store in this issue. +- Send mail, create tickets, deliver notifications, call provider SDKs, or run + background sync. +- Connect to authentication, wallet flows, Stellar transactions, or payment + features. +- Change shared design-system files or global app state. +- Modify files outside this folder for this issue. + +## Review Checklist + +- [ ] All changed files are under `tools/v1/team/customer-support-macro-tool/`. +- [ ] `specs.md`, `docs/ARCHITECTURE.md`, and `docs/DATA_OWNERSHIP.md` agree + on the folder boundary. +- [ ] No relative imports resolve outside this folder. +- [ ] No main app routing, inbox, compose, auth, wallet, Stellar, database, + provider, notification, or design-system wiring is introduced. +- [ ] `node --test tools/v1/team/customer-support-macro-tool/tests/architecture-contract.test.mjs` + passes. +- [ ] Existing Vitest suites still pass. + +## Acceptance Criteria Mapping + +| Issue Requirement | Evidence | +| -------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| Clear folder-local architecture plan | This document and `specs.md` | +| No main app, routing, inbox, wallet, Stellar, database, or design-system changes | Dependency rules and contract test | +| Specs explain future contributor boundaries | `specs.md` and Future Contributor Contract | +| Files changed are limited to this folder | Git diff and contract test | +| Self-contained mini-product review | README, docs, fixtures, services, hooks, and tests are folder-local | diff --git a/tools/v1/team/customer-support-macro-tool/docs/DATA_OWNERSHIP.md b/tools/v1/team/customer-support-macro-tool/docs/DATA_OWNERSHIP.md new file mode 100644 index 00000000..14c91dbb --- /dev/null +++ b/tools/v1/team/customer-support-macro-tool/docs/DATA_OWNERSHIP.md @@ -0,0 +1,113 @@ +# Customer Support Macro Tool - Data Ownership + +This document defines owned data, runtime state, storage boundaries, and future +adapter rules for the folder-local Customer Support Macro Tool. + +## Owned Data + +| Data | Owner | Source | Mutation Rule | +| ----------------------------------------- | ----------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------- | +| `Macro` | `services/macro.service.ts` | Fixture seed, caller input, or storage adapter data | Service functions return copied objects or arrays and do not mutate inputs. | +| `MacroStore` | `services/macro.service.ts` | Local macro arrays | Treated as an in-memory shape, not a server schema. | +| `MacroCreateInput` and `MacroUpdateInput` | `services/macro.service.ts` | User or caller input | Validated locally before save. | +| `MacroSearchOptions` | `services/macro.service.ts` | Hook or caller state | Used only for local filtering. | +| `StorageAdapter` | `services/storage.service.ts` | Browser localStorage or in-memory adapter | Adapter writes are scoped to `csmt_macros_v1`. | +| Hook state | `hooks/useMacros.ts` | Local React state | Persisted only through the provided adapter. | + +## Runtime State + +The current tool owns only local state: + +- Macro arrays in `useMacros()`. +- Search options, sort key, and sort direction in `useMacros()`. +- Browser localStorage values under `csmt_macros_v1` when the default adapter is + used. +- In-memory adapter maps for tests. +- Fixture macro arrays for local test and review. + +The tool does not own: + +- Live inbox messages. +- Compose drafts. +- Sent mail. +- Ticket records. +- Customer profiles. +- Team permissions. +- Server database rows. +- Notification delivery state. +- Wallet accounts. +- Stellar transactions. + +## Lifecycle + +```text +Seed fixtures or adapter-loaded macros + -> useMacros() state + -> macro.service.ts pure operation + -> new macro array + -> saveMacros(adapter) + -> local adapter storage only +``` + +No step writes to server APIs, shared app stores, queues, databases, inbox +state, compose state, notification services, wallet state, or Stellar ledgers. + +## Fixture Rules + +- Fixture macros are synthetic examples for tests and local development. +- Fixture text must not include real customers, tickets, orders, invoices, + secrets, API keys, access tokens, or production email content. +- Tests may import fixtures but must not write back to fixture files. +- New fixtures should stay small and reviewable. + +## Storage Ownership + +`services/storage.service.ts` owns the current persistence boundary: + +- `localStorageAdapter` uses browser `localStorage` when available. +- `createInMemoryAdapter()` supports tests and isolated review. +- `loadMacros()` safely falls back to an empty array for missing or malformed + data. +- `saveMacros()` serializes only macro arrays. +- `clearMacros()` removes only the tool storage key. + +Storage does not represent a database schema, team-sharing protocol, or +cross-device sync model. Any server-backed storage requires a separate +integration issue. + +## Hook Ownership + +`hooks/useMacros.ts` owns local UI state orchestration: + +- It seeds local state from fixtures when storage is empty. +- It persists macro state through the selected storage adapter. +- It exposes add, edit, remove, favorite, use, search, sort, and validate + actions. + +The hook does not own routing, permissions, user identity, team membership, +compose insertion, inbox mutation, or mail sending. + +## Future Integration Adapter Boundary + +If a later issue connects this tool to live app data, it must add an explicit +adapter or wrapper layer and review: + +- Agent authentication and team authorization. +- Server persistence and sync conflict handling. +- Audit history and retention rules. +- Compose-window insertion behavior. +- Variable prompts for missing customer or ticket values. +- Privacy rules for macro content copied from real support threads. +- Rate limits and provider error handling if remote storage is used. + +The current architecture issue documents those boundaries but does not implement +the adapter. + +## Privacy and Security Constraints + +- Do not log real support replies, customer details, or ticket content. +- Do not include secrets, tokens, API keys, wallet data, production IDs, or real + customer data in fixtures. +- Do not send macro output automatically. +- Do not persist macros outside the configured local adapter. +- Keep future error messages structural and non-sensitive. diff --git a/tools/v1/team/customer-support-macro-tool/specs.md b/tools/v1/team/customer-support-macro-tool/specs.md index 48fc1c7c..c8b1dcfd 100644 --- a/tools/v1/team/customer-support-macro-tool/specs.md +++ b/tools/v1/team/customer-support-macro-tool/specs.md @@ -1,72 +1,90 @@ -# Customer Support Macro Tool — Specs +# Customer Support Macro Tool Specs ## Purpose -Reusable response templates for customer support agents. -Agents define macros once and apply them across many conversations, -with variable interpolation (`{{customer_name}}`, etc.) for personalisation. +Reusable response templates for customer support agents. Agents define macros +once and apply them across many conversations with variable interpolation such +as `{{customer_name}}` and `{{ticket_id}}`. ## Scope -- **Release tier:** V1 -- **Audience:** Team -- **Folder ownership:** `tools/v1/team/customer-support-macro-tool/` +- Release tier: V1 launch tool +- Audience: team +- Folder ownership: `tools/v1/team/customer-support-macro-tool/` +- Integration status: isolated mini-product workspace -This is a self-contained tooling workspace. Do not wire this tool into the main -app, routing, inbox architecture, wallet core, Stellar core, or design system -unless a future integration issue explicitly allows it. +This is a self-contained tool workspace. Do not wire this tool into the main +app, routing, inbox architecture, wallet core, Stellar core, database schema, or +shared design system unless a future integration issue explicitly allows it. -## Recommended internal structure +## Module Boundaries -``` -customer-support-macro-tool/ -├── components/ # React UI (future issue) -├── services/ # Pure business logic (implemented) -├── hooks/ # React hooks (implemented) -├── tests/ # Unit tests + test plan (implemented) -├── fixtures/ # Local test data (implemented) -└── docs/ # Setup and review guides (implemented) -``` +| Module | Purpose | Change Rules | +| ----------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | +| `services/macro.service.ts` | Pure CRUD, validation, search, sorting, variable extraction, and interpolation logic | Keep the service deterministic and side-effect free. | +| `services/storage.service.ts` | Folder-local `StorageAdapter`, browser localStorage adapter, and in-memory adapter | Do not replace this with database or app storage in this issue. | +| `hooks/useMacros.ts` | React hook that binds macro services to local state and storage adapter persistence | Keep integration through explicit adapter options; do not import app stores. | +| `fixtures/` | Local macro examples for tests and development | Do not include real customers, real tickets, secrets, or production content. | +| `tests/` | Unit tests, test plan, and architecture contract checks | Keep tests folder-local and independent from main app setup. | +| `docs/` | Setup, review notes, architecture, and data ownership guidance | Document future integration boundaries instead of implementing them here. | -## Required issue categories +Future `components/` files may be added inside this folder by a UI-specific +issue, but this architecture issue does not mount any UI into the app shell. -| Category | Status | -| ------------------------- | ------------------------------------------------------------------------- | -| Architecture | Addressed — service layer, storage adapter pattern, hook interface | -| Feature | Addressed — CRUD, search, sort, interpolation, validation, usage tracking | -| UI and accessibility | Deferred — separate UI issue | -| Security and performance | Addressed — input validation, no external deps, immutable patterns | -| Testing and documentation | ✅ Completed — this issue | +## Required Issue Categories -## Macro categories +| Category | Status | +| ------------------------- | --------------------------------------------------------------------------------------------------------------- | +| Architecture | Addressed through `docs/ARCHITECTURE.md`, `docs/DATA_OWNERSHIP.md`, and `tests/architecture-contract.test.mjs`. | +| Feature | Existing service layer covers CRUD, search, sort, interpolation, validation, and usage tracking. | +| UI and accessibility | Deferred to a separate UI issue. | +| Security and performance | Existing validation, local fixtures, adapter boundaries, and no external provider calls. | +| Testing and documentation | Existing Vitest suites plus the architecture contract test and docs. | -The tool supports the following six macro categories: +## Macro Categories -- `greeting` — welcome and introduction messages -- `billing` — invoices, payment confirmations -- `technical` — password resets, bug escalations -- `shipping` — order status, dispatch, tracking -- `refund` — refund approvals and status -- `general` — catch-all and closing messages +The tool supports these macro categories: -## Variable interpolation syntax +- `greeting`: welcome and introduction messages +- `billing`: invoices and payment confirmations +- `technical`: password resets and bug escalations +- `shipping`: order status, dispatch, and tracking +- `refund`: refund approvals and status +- `general`: catch-all and closing messages + +## Variable Interpolation Syntax Variables use double-curly-brace syntax: -``` +```text Hi {{customer_name}}, your ticket {{ticket_id}} has been resolved. ``` Variables are extracted via `extractVariables(body)` and interpolated via `interpolateMacro(body, variableMap)`. -## Contributor boundary +## Contributor Boundary -All work for this tool stays in: +Future contributors may change: -``` -tools/v1/team/customer-support-macro-tool/ -``` +- Folder-local docs, tests, fixtures, and service internals. +- Macro validation, search, sorting, and interpolation rules when matching tests + are updated. +- Storage adapter implementations when they remain inside this folder and keep + the `StorageAdapter` contract. +- Future `components/` files inside this folder when a scoped UI issue asks for + them. + +Future contributors may not change in this issue: + +- Main application shell, dashboard layout, navigation, or routing. +- Existing inbox architecture, mail rendering engine, or compose-send flow. +- Authentication, wallet core, Stellar core, payment flows, or database schema. +- Shared design system files or global style tokens. +- Server APIs, provider SDKs, webhooks, notification delivery, or background + jobs. +- Files outside `tools/v1/team/customer-support-macro-tool/`. -Pull requests that modify files outside this folder will be rejected unless -a future integration issue explicitly grants expanded scope. +Any future integration issue must define authorization, team-sharing rules, +server persistence, audit history, and compose-window insertion behavior before +this tool touches live mail data. diff --git a/tools/v1/team/customer-support-macro-tool/tests/architecture-contract.test.mjs b/tools/v1/team/customer-support-macro-tool/tests/architecture-contract.test.mjs new file mode 100644 index 00000000..d3d687e5 --- /dev/null +++ b/tools/v1/team/customer-support-macro-tool/tests/architecture-contract.test.mjs @@ -0,0 +1,163 @@ +import assert from "node:assert/strict"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { dirname, extname, isAbsolute, relative, resolve } from "node:path"; +import { describe, it } from "node:test"; +import { fileURLToPath } from "node:url"; + +const toolRoot = fileURLToPath(new URL("..", import.meta.url)); +const toolPath = "tools/v1/team/customer-support-macro-tool/"; + +const requiredDocs = [ + "README.md", + "specs.md", + "docs/ARCHITECTURE.md", + "docs/DATA_OWNERSHIP.md", + "docs/REVIEW_NOTES.md", + "docs/SETUP.md", + "tests/TEST_PLAN.md", +]; + +const contractDocs = ["specs.md", "docs/ARCHITECTURE.md", "docs/DATA_OWNERSHIP.md"]; +const codeFiles = listFiles(toolRoot).filter((file) => + [".js", ".mjs", ".ts", ".tsx"].includes(extname(file)), +); + +describe("Customer Support Macro Tool - architecture contract", () => { + it("keeps required architecture documents folder-local", () => { + for (const doc of requiredDocs) { + const absolute = resolve(toolRoot, doc); + assert.ok(existsSync(absolute), `${doc} must exist`); + assert.ok(isPathInside(toolRoot, absolute), `${doc} must stay inside ${toolPath}`); + } + }); + + it("documents module boundaries, data ownership, and integration constraints", () => { + assertDocIncludes("docs/ARCHITECTURE.md", [ + toolPath, + "Module Boundaries", + "Dependency Rules", + "Future Contributor Contract", + "No network calls", + "No persistence writes", + "compose", + "inbox", + ]); + + assertDocIncludes("docs/DATA_OWNERSHIP.md", [ + "Macro", + "MacroStore", + "StorageAdapter", + "Fixture Rules", + "Future Integration Adapter Boundary", + "No step writes to server APIs", + ]); + + assertDocIncludes("specs.md", [ + "Module Boundaries", + "Future contributors may change", + "Future contributors may not change", + "Files outside", + toolPath, + ]); + }); + + it("removes broken generation/template leftovers from contract docs", () => { + const forbidden = ["$(", "$dir", "Set-Content", "System.Collections.Hashtable", "@ |"]; + + for (const relativePath of contractDocs) { + const text = readFileSync(resolve(toolRoot, relativePath), "utf8"); + for (const token of forbidden) { + assert.ok(!text.includes(token), `${relativePath} still contains ${token}`); + } + } + }); + + it("keeps relative imports inside the tool folder", () => { + for (const file of codeFiles) { + const imports = extractImportSpecifiers(readFileSync(file, "utf8")); + + for (const specifier of imports) { + if (!specifier.startsWith(".")) continue; + const resolved = resolve(dirname(file), specifier); + assert.ok( + isPathInside(toolRoot, resolved), + `${relative(toolRoot, file)} imports outside the tool folder: ${specifier}`, + ); + } + } + }); + + it("does not import forbidden main app or provider modules", () => { + const forbiddenImportFragments = [ + "@/", + "src/", + "routes/", + "router", + "inbox", + "mailbox", + "compose", + "notification", + "provider", + "wallet", + "stellar", + "database", + "components/ui", + "design-system", + ]; + + for (const file of codeFiles) { + const imports = extractImportSpecifiers(readFileSync(file, "utf8")); + for (const specifier of imports) { + for (const fragment of forbiddenImportFragments) { + assert.ok( + !specifier.toLowerCase().includes(fragment), + `${relative(toolRoot, file)} imports forbidden module fragment ${fragment}`, + ); + } + } + } + }); +}); + +function assertDocIncludes(relativePath, expectedPhrases) { + const text = readFileSync(resolve(toolRoot, relativePath), "utf8"); + for (const phrase of expectedPhrases) { + assert.ok(text.includes(phrase), `${relativePath} must mention "${phrase}"`); + } +} + +function extractImportSpecifiers(source) { + const specifiers = []; + const patterns = [ + /\bfrom\s+["']([^"']+)["']/g, + /\bimport\s+["']([^"']+)["']/g, + /\bimport\s*\(\s*["']([^"']+)["']\s*\)/g, + ]; + + for (const pattern of patterns) { + for (const match of source.matchAll(pattern)) { + specifiers.push(match[1]); + } + } + + return specifiers; +} + +function listFiles(root) { + const files = []; + for (const entry of readdirSync(root)) { + const fullPath = resolve(root, entry); + const stats = statSync(fullPath); + if (stats.isDirectory()) { + files.push(...listFiles(fullPath)); + } else { + files.push(fullPath); + } + } + return files; +} + +function isPathInside(root, target) { + const rel = relative(root, target); + return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel)); +}