snapshot of https://github.com/marmelab/atomic-crm.git (main, 2026-09-18; blobs > 131072B omitted)
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
Some checks failed
✅ Check / 🔬 ESLint (push) Failing after 21s
✅ Check / 🏷️ Typecheck (push) Failing after 11s
✅ Check / 🔎 Test (push) Failing after 11s
✅ Check / e2e-test (push) Failing after 11s
✅ Check / 🔨 Build (push) Failing after 11s
🚀 Deploy / 🚀 Deploy (doc) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (demo) (push) Failing after 12s
🚀 Deploy / 🚀 Deploy (supabase) (push) Failing after 18s
This commit is contained in:
101
.claude/skills/delete-initial-resource/SKILL.md
Normal file
101
.claude/skills/delete-initial-resource/SKILL.md
Normal file
@@ -0,0 +1,101 @@
|
||||
---
|
||||
name: delete-initial-resource
|
||||
description: Remove one or more of the initial CRM resources (contacts, companies, deals, tags, tasks) from the codebase. Use when the user asks to delete, remove, or strip out one or several of these built-in resources. Runs the delete-initial-resource.ts script to drop each resource's own folder, then guides cleanup of every file that references them.
|
||||
---
|
||||
|
||||
# delete-initial-resource
|
||||
|
||||
## Overview
|
||||
|
||||
Removes one or more of the five initial Atomic CRM resources (`contacts`, `companies`, `deals`, `tags`, `tasks`) and every reference to them. **Irreversible confirm the target(s) with the user first; rely on git to recover.** Exit criterion: `make typecheck && make lint` is clean and no live reference to the deleted resource(s) survives in `src/` or `supabase/`.
|
||||
|
||||
## When to Use
|
||||
|
||||
- The user asks to delete, remove, or strip out one or several built-in CRM resources.
|
||||
- Not for *custom* entities added during setup only the five built-ins above.
|
||||
|
||||
For the backend migration mechanics, see `Skill({skill: "backend-dev"})` and `Skill({skill: "writing-migrations"})`.
|
||||
|
||||
## Steps
|
||||
|
||||
1. **Confirm the target(s)**, then read each one's file before editing:
|
||||
- `contacts` → [`contacts.md`](contacts.md) — spine; **confirm cascade scope first**
|
||||
- `companies` → [`companies.md`](companies.md) — spine/link; **confirm cascade scope first**
|
||||
- `deals` → [`deals.md`](deals.md)
|
||||
- `tags` → [`tags.md`](tags.md)
|
||||
- `tasks` → [`tasks.md`](tasks.md)
|
||||
2. **Run the script** (below) — deletes each `src/components/atomic-crm/<resource>/` folder and prints the dependent files to clean.
|
||||
3. **Clean each dependent file** using the "Shapes" patterns below + the per-resource file.
|
||||
4. **Verify** (below).
|
||||
|
||||
Deleting several at once: clean from the most-coupled resource outward; clean a shared file **once** for both; and reconcile interacting union guidance — deleting both `contacts` and `deals` removes the notes/activity-log reference *entirely* rather than each narrowing it, so don't half-apply each file's "narrow the union" note.
|
||||
|
||||
## The script
|
||||
|
||||
```bash
|
||||
node .claude/skills/delete-initial-resource/delete-initial-resource.ts <resource> [<resource> ...]
|
||||
```
|
||||
|
||||
Validates the names, deletes each resource's folder, and prints the **merged, de-duplicated dependent-file list** (files inside a just-deleted folder are dropped). It does **not** edit those files — that's your job. Each entry in the script's `dependentFiles` map has a `//` comment saying *what* to remove; read it alongside the printed list, and update the map if the codebase grows new references.
|
||||
|
||||
## Shapes a resource takes
|
||||
|
||||
A resource is rarely just a folder. The later shapes are invisible to a `\b<resource>\b` grep:
|
||||
|
||||
- **Standalone** — imports, JSX, routes, menu/nav, dashboard widgets, the `<Resource>` entry in `root/CRM.tsx`. Wrapper components *outside* the folder that only render the resource: delete outright, then clean importers.
|
||||
- **Field/column on another record** — fans out far wider: the `types.ts` type, `providers/commons/mergeContacts.ts`, CSV/JSON import, i18n, stories/tests, `StoryWrapper` builders, and the sample CSVs (header **and** every row). For a *link* column, decide per-link: orphaned shell (typed, defaulted) or drop — remove the UI either way.
|
||||
- **Aggregated `nb_<resource>`** (from the `_summary` views) lands on a record type, read across shows/lists/filters/generators. **Invisible to a `\b<resource>\b` grep** (the `_` is a word char) — grep `nb_<resource>` and the denormalized `company_name` separately.
|
||||
- **Shared subsystems** — notes (`reference: "contacts" | "deals"`) and the activity log (`"company" | "contact" | "deal" | "all"` union). Narrow the union, delete the per-type render components (`ActivityLog*Created.tsx`) + dead branches, prune the `providers/commons/activity.ts` fetcher + `consts.ts`/`types.ts` members. Surviving `=== "<other>"` checks stay valid — only the deleted member's branches go.
|
||||
- **Config props** (`taskTypes`, `dealStages`, `companySectors`) live in `root/defaultConfiguration.ts`, `ConfigurationContext.tsx`, `CRM.tsx` (default + jsdoc + store seed), `App.tsx`, and `SettingsPage.tsx` (the `<Card>` **and** the section-list entry). **Not always named after the resource** — grep the settings `<Card>` for other keys (Deals owned `currency`) and flag borderline-general ones rather than dropping them silently.
|
||||
- **Custom dataProvider methods** live in **both** providers; `CrmDataProvider` derives from the supabase one, so removing them there drops them from the type. Also clean the lifecycle-callback blocks in both providers, the `sales` `beforeDelete` reassignment, the supabase `<resource> -> <resource>_summary` `getList` routing, and the FakeRest generators (which *drive* record creation).
|
||||
- **JSON importer** (`misc/useImportFromJson.ts`) treats `contacts`/`companies`/`notes`/`tasks` as top-level types (`import<X>` fn, `$.<x>.*` path, a `TYPES` arm + switch, guards, `stats`/`failedImports`/`idsMaps` keys). Mirror in `ImportPage.tsx` + `import-sample.json`; the CSV importer (`useContactImport.tsx`) has parallel mappings.
|
||||
- Check `useCallback`/`useMemo` deps — a stale `getTags`/`getCompanies` there is invisible until `tsc`.
|
||||
|
||||
## Backend (the script only touches frontend)
|
||||
|
||||
See the `backend-dev` skill for the migration workflow. In `supabase/schemas/`:
|
||||
|
||||
- Drop/adjust the table + view, generate a migration (`npx supabase db diff --local -f remove_<resource>`); update `contacts_summary`/`companies_summary` if they aggregated it.
|
||||
- **`activity_log`** is a `UNION ALL` with the same column set per branch — remove the resource's columns from **every** branch (keep them aligned), delete its dedicated branch(es), and drop the matching snake→camel rename in the supabase `getList("activity_log")`.
|
||||
- `06_grants.sql` grants the table **and** its `<resource>_id_seq` (drop both). `05_policies.sql` has an RLS-enable line **and** `create policy` statements (drop both).
|
||||
- A resource used inside a SQL function (`merge_contacts`): drop those lines + the unused local var. Editing `02_functions.sql` regenerates the whole function (expected) — renumber leftover step comments.
|
||||
- **Orphan check:** deleting triggers can orphan helpers — but verify shared callers first (`get_domain_favicon` survives a `companies` delete because contact avatars also call it).
|
||||
- **Edge functions** are separate and easy to miss: `merge_contacts/`, `_shared/db.ts` (Kysely types), `postmark/` (inbound email), and `mcp/` (an AI subsystem whose depth varies per resource). When a surviving function loses a capability, **flag it to the user**.
|
||||
|
||||
## Rationalizations
|
||||
|
||||
| Rationalization | Reality |
|
||||
|---|---|
|
||||
| "I deleted the folder, the resource is gone." | A resource is also fields on other records, `nb_<resource>` aggregates, shared subsystems, config props, dataProvider methods, and SQL — the folder is the easy part. |
|
||||
| "A `\b<resource>\b` grep came back clean, so it's clean." | `nb_<resource>` and denormalized `company_name` are invisible to that grep (`_` is a word char). Grep them separately. |
|
||||
| "I'll remove the i18n key from English only." | The French catalog is type-checked against English — a one-sided removal is a `tsc` error. Remove from both or neither. |
|
||||
| "The script edits the dependent files for me." | The script only deletes folders and prints the list. Editing every dependent file is your job. |
|
||||
| "Typecheck passes, so the deletion is complete." | `tsc` misses broken views/functions/grants. Reset the DB and grep for live references too. |
|
||||
|
||||
## Red Flags
|
||||
|
||||
- Editing files inside `supabase/migrations/` (never — they are append-only history).
|
||||
- A one-sided i18n key removal (English without French, or vice versa).
|
||||
- Relying on a single `\b<resource>\b` grep and skipping `nb_<resource>` / `company_name`.
|
||||
- Deleting a shared SQL helper or edge-function capability without checking surviving callers.
|
||||
- Deleting several resources but half-applying each file's "narrow the union" note.
|
||||
- Skipping the `db reset` check when Supabase is running.
|
||||
|
||||
## Verification
|
||||
|
||||
```bash
|
||||
make typecheck && make lint
|
||||
```
|
||||
|
||||
Resolve whatever `tsc` surfaces (the dependent-file list is a guide, not a guarantee). Then `grep -rniE "\b<resource>\b"` over `src/`+`supabase/`, plus **separate** greps for `nb_<resource>`, `company_name`, and config-prop names (`<resource>Types`, `companySectors`, `currency`). Watch for benign/substring false positives (each resource file lists its own); never edit `supabase/migrations/`.
|
||||
|
||||
- [ ] `make typecheck && make lint` is clean.
|
||||
- [ ] `\b<resource>\b`, `nb_<resource>`, `company_name`, and config-prop greps return only benign matches.
|
||||
- [ ] i18n keys removed from both catalogs, or neither.
|
||||
- [ ] Backend: table/view/policies/grants/sequences and any function references handled in `supabase/schemas/` (not `migrations/`).
|
||||
- [ ] If Supabase is running, `npx supabase db reset --local` replays cleanly.
|
||||
- [ ] Any lost edge-function capability flagged to the user.
|
||||
|
||||
**i18n is all-or-nothing:** `frenchCrmMessages.ts` is type-checked against the type derived from `englishCrmMessages.ts`, so remove a key from **both** catalogs or **neither** (a one-sided removal is a `tsc` error). Dead `resources.<x>.*` keys are harmless — flag rather than force. `frenchCrmMessages.ts` uses literal `…` escapes; if an exact-string `Edit` fails on a block spanning one, fall back to `sed -i '<from>,<to>d'`.
|
||||
|
||||
If Supabase is running, `npx supabase db reset --local` replays the chain + `seed.sql` and catches a broken view/function/grant `db diff` misses (benign `index "…_pkey" does not exist` NOTICEs expected). On Node 22 (see `.nvmrc`), `make test` (vitest) is also available.
|
||||
26
.claude/skills/delete-initial-resource/companies.md
Normal file
26
.claude/skills/delete-initial-resource/companies.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Deleting `companies`
|
||||
|
||||
A **spine/link** resource: it owns the `companies` table *and* is a `company_id` link on **both** `contacts` and `deals`, its `companies_summary` view aggregates `nb_contacts`/`nb_deals`, and it adds a **denormalized `company_name`** to the `Contact` type. So it fans out far wider than its folder (≈50 frontend files + backend).
|
||||
|
||||
## Confirm cascade scope first
|
||||
**Remove `company_id` fully** — drop the column from the `Contact`/`Deal` types, the CSV import/export, `mergeContacts`, the fakerest generators, and i18n. This full removal is what makes the deletion large.
|
||||
|
||||
## Frontend specifics
|
||||
- **Activity log** — a dedicated `company.created` branch (delete `ActivityLogCompanyCreated.tsx` outright + the branch in `ActivityLogIterator.tsx` + `COMPANY_CREATED` in `consts.ts`/`types.ts`) **and** a `"company"` member in the context union. Narrowing the union turns the `=== "company"` checks in `ActivityLogContactCreated`/`ActivityLogContactNoteCreated`/`ActivityLogDealCreated`/`ActivityLogDealNoteCreated` into type errors — prune those branches (the `company_id` "to/at `<company>`" ReferenceField + the `CompanyAvatar` in the deal-note header). `ActivityLog.tsx` has a `companyId` prop + `company_id` filter; `providers/commons/activity.ts` has a `getNewCompanies` fetcher + `companyId` param.
|
||||
- **`company_id` link** on contacts (`ContactInputs` selector, `ContactShow`/`ContactListContent` avatar+name, the exporter's `fetchRelatedRecords` + the `company` CSV column, `contactModel.ts`'s vCard `ORG:` line, `ExportVCardButton.tsx`) and on deals (`DealInputs` required selector, `DealCard`/`DealShow`/`DealEdit` avatar, `DealList` filter), plus the note-header `CompanyAvatar` in `notes/Note.tsx`.
|
||||
- **Denormalized `company_name`** (from `companies_summary`'s `c.name as company_name`) — read in `ContactOption.tsx`, `deals/ContactList.tsx`, `dashboard/HotContacts.tsx`, and the `position_at_company` rendering. **Invisible to a `\bcompan(y|ies)\b` grep** — grep `company_name` separately.
|
||||
- **`companySectors` config prop** — see SKILL.md "Shapes a resource takes" for the standard config-prop locations. Companies-specific: the `<Card>` holds only a `companySectors` `ArrayInput` (no `validateItemsInUse`), and it's also read in `useImportFromJson.ts` to validate an imported `sector`.
|
||||
- **Providers** — supabase `getList`/`getOne` route `companies -> companies_summary` (drop both); the contacts FTS list adds `company_name`. FakeRest has `processCompanyLogo` (favicon), `fetchAndUpdateCompanyData` (stamps `company_name` onto contacts, called from the contacts `beforeCreate`/`beforeUpdate`), `updateCompany` (maintains `nb_contacts`/`nb_deals` from the contacts/deals `afterCreate`/`afterDelete`), the whole `companies` callback block, the `companies` arm of the `sales` `beforeDelete` reassignment, and the `getActivityLog(dataProvider, companyId, salesId)` call (drop the `companyId` arg + the `companyId`/`company_id` filter plumbing in `providers/commons/activity.ts`). Delete `providers/commons/getCompanyAvatar.ts` + test.
|
||||
- **FakeRest generators are deeply company-coupled** — `dataGenerator/companies.ts` deleted outright + dropped from `index.ts` (mind the order: sales → tags → [companies] → contacts → …) and `types.ts` (`Db.companies`). `contacts.ts` *picks a company* to derive `company_id`/`company_name`/`sales_id`/`first_seen` (+ a per-company `maxContacts` cap), and `deals.ts` *picks a company* then filters contacts by `company_id` for `contact_ids` — rewrite both to pick a random `db.sales` + `randomDate()` (and random contacts for deals). The `faker` `company` namespace import is unrelated — keep it.
|
||||
- **JSON importer** treats `companies` as a top-level type (`importCompany`, `$.companies.*`, `CompanyImport`/`isCompany`, `mapSizeToCategory`, `companySectors` validation, stats/idsMaps keys) + the `company_id` mapping on imported contacts. Mirror in `ImportPage.tsx` + `import-sample.json`. CSV importer (`useContactImport.tsx`) has a `getCompanies` cache + a `company` column → `company_id`.
|
||||
- **Mislabeled i18n:** `deals/OnlyMineInput.tsx` uses `resources.companies.filters.only_mine` ("Only companies I manage") even though it filters deals — re-point it to a deals key, don't delete the input.
|
||||
|
||||
## Backend
|
||||
- Drop the `companies` table + `companies_id_seq` grant + RLS-enable line + its 4 policies + table grants. Drop `company_id` on **both** `contacts` and `deals` (FKs `contacts_company_id_fkey`/`deals_company_id_fkey` + indexes `contacts_company_id_idx`/`deals_company_id_idx`). Drop the `companies_summary` view (+ grant). Update `contacts_summary`: drop `co.company_id`, the `c.name as company_name` column, the `left join public.companies`, and narrow the `group by` to `co.id`.
|
||||
- `activity_log` view carries the company pattern (a `company` column on **every** branch **and** a dedicated `company.created` branch) — remove the `company` + `company_id` columns from all branches (the `contactNote`/`dealNote` branches only joined a table to fetch `company_id`, so drop those now-unused joins) and delete the branch.
|
||||
- Drop the company-only function `handle_company_saved` (favicon-from-website trigger) + its `company_saved` trigger + grant, and the `set_company_sales_id_trigger`. Drop the `company_id` line from `merge_contacts`. **Orphan subtlety:** `handle_company_saved` calls `get_domain_favicon`, but so does `get_avatar_for_email` (contact avatars) — so it is **not** orphaned; **keep** `get_domain_favicon` and the `favicons_excluded_domains` table.
|
||||
- **Edge functions:** unlike `contacts` (delete the whole function), **keep** `postmark/` and surgically remove the company bits — it auto-creates a company from the sender's email domain (`getOrCreateCompanyFromDomain` in `addNoteToContact.ts`, `extractCompanyName` in `extractMailContactData.ts`) and links the new contact via `company_id`. Delete `getOrCreateCompanyFromDomain`, drop the `domain`/`companyName`/`website` params threaded through `getOrCreateContactFromEmailInfo`/`addNoteToContact`/`index.ts`, drop `company_id` from the contact insert, remove `extractCompanyName` + the `companyName`/`website` output fields + the now-unused `tldts` import from `extractMailContactData.ts`, and update the postmark tests (`addNoteToContact.test.ts`, `extractMailContactData.test.ts`). **Flag that auto-company-creation on inbound email is gone but contact+note creation still works.** Elsewhere: `_shared/db.ts` declares `company_id` on **both** `ContactsTable` and `DealsTable` (no dedicated `CompaniesTable`); `merge_contacts/index.ts` merges `company_id` in `mergeContactData`; `mcp/index.ts` + `mcp/validateSql.test.ts` mention `companies`/`companies_summary` in prompt examples/fixtures (swap for a surviving table). No `config.toml` block changes are needed (postmark + merge_contacts both survive).
|
||||
|
||||
## grep — benign matches to leave
|
||||
- The `faker` `company` namespace import in `dataGenerator/contacts.ts` (used for `bsAdjective()` titles), the `reference="companies"`/`source="company_id"` JSDoc examples in the **mutable** `admin/reference-input.tsx`/`admin/list.tsx`, and email-domain fixtures like `@company.com`/`original.recipient@company.com` in test bodies.
|
||||
- Dead i18n keys (`resources.companies.*`, `position_at_company`, `you_added_company`/`at_company`, `crm.settings.companies.*`) — flag, don't force (both catalogs or neither). `test-data/contacts.csv` keeps `company_id`/`company_name`/`company` columns — the importer ignores them, so leaving them is harmless; dropping means editing the header **and** all ~500 rows — flag it.
|
||||
29
.claude/skills/delete-initial-resource/contacts.md
Normal file
29
.claude/skills/delete-initial-resource/contacts.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Deleting `contacts`
|
||||
|
||||
**The spine of the CRM and the widest deletion of the five.** Notes attach to `contacts | deals`, tasks belong only to contacts, `tags` is a field on `Contact`, companies aggregate `nb_contacts`, deals link `contact_ids`, and the activity-log + JSON-import + merge subsystems all revolve around it. Removing it orphans notes/tasks/tags.
|
||||
|
||||
## Confirm cascade scope first
|
||||
Ask the user how far to cascade — the answer changes the whole job:
|
||||
- **contacts only** — leave notes/tasks/tags as compiling shells (narrowing the notes union to `"deals"` keeps notes valid as long as deals exists; tasks/tags become orphaned shells), or
|
||||
- **also delete the now-orphaned subsystems**.
|
||||
- And whether to touch the backend.
|
||||
|
||||
## Frontend specifics
|
||||
- **Shared subsystems** — narrow the notes union to `"deals"` and the activity-log union to drop `"contact"`. Delete `ActivityLogContactCreated.tsx` + `ActivityLogContactNoteCreated.tsx` outright; drop `getNewContactsAndNotes` in `providers/commons/activity.ts`.
|
||||
- **Typing landmine:** `DealNote.status` is typed `undefined` (a deliberate ContactNote-compat marker in `types.ts`), so retyping `NoteInputs`'s `useFormContext<ContactNote | DealNote>` down to `<DealNote>` collapses `status` to `never` and `tsc` rejects the status-hydration `setValue("status", …)`. Widen it back with `useFormContext<Omit<DealNote, "status"> & { status?: string }>()`.
|
||||
- **Contact-only *mobile* note components** (dead once contacts is gone) — delete outright: `NoteCreateSheet.tsx`, `NoteEditSheet.tsx`, `NoteShowPage.tsx`, `NotesIteratorMobile.tsx`, `NoteInputsMobile.tsx` (+ their stories/tests). Drop the `NotesIteratorMobile` re-export from `notes/index.ts` and clean importers (`MobileNavigation.tsx`, the mobile `<Resource name="contacts">` note route in `CRM.tsx`).
|
||||
- **Links to contacts:** `deals.contact_ids` (a selector in `DealInputs.tsx`, a per-deal `<ContactList/>` in `DealShow.tsx` + the standalone `deals/ContactList.tsx` to delete, the "create a contact first" gate in `DealEmpty.tsx`) and `tasks.contact_id` (the whole task contact-linking UI: `AddTask`, `Task`, `TaskCreateSheet`, `TaskEditSheet`, `TaskFormContent`'s `selectContact`, the `showContact`/`filterByContact` threading in `TasksIterator`/`TasksListFilter`/`TasksListByDueDate`). Decide per-link: orphaned shell (`contact_ids: Identifier[]`, default `[]`) vs drop.
|
||||
- **Custom method:** `mergeContacts(sourceId, targetId)` in both providers (FakeRest → `providers/commons/mergeContacts.ts`, delete it; supabase → the `merge_contacts` edge function). Also `processContactAvatar`/`getContactAvatar` (delete `providers/commons/getContactAvatar.ts` + test), the `contacts`/`contact_notes` + `tasks` `nb_tasks` callbacks, and the supabase `contacts -> contacts_summary` routing + `contact_note -> contactNote` remap.
|
||||
- **Wrapper components** outside the folder, delete outright: `dashboard/HotContacts.tsx` (contacts widget), `dashboard/DashboardStepper.tsx` (contact-onboarding wizard). `Dashboard.tsx`/`MobileDashboard.tsx` gate the whole render on contact/contactNote counts via the stepper — drop that gating. `LatestNotes.tsx` mixes contact_notes + a `<Contact>` renderer; `dashboard/TasksList.tsx` has an `<AddTask selectContact/>`.
|
||||
- **JSON importer** is heavily contact-coupled (`importContact`/`importNote` for `contact_notes`, `contactGender`, stats/idsMaps); also `import-sample.json` (drop the contacts + notes sections). `ContactOption.tsx` is a shared option renderer — delete it.
|
||||
- FakeRest generators: delete `contacts.ts`, `contactNotes.ts`, `finalize.ts` outright; clean `index.ts`/`types.ts`; `deals.ts` (contact_ids), `tasks.ts` (contact_id + nb_tasks), `companies.ts` (nb_contacts seed).
|
||||
- Layout/login: `Header.tsx`, `MobileNavigation.tsx`, `SignupPage.tsx` (`redirectTo: "/contacts"`), `StoryWrapper.tsx` (`buildContact` + the contacts/contact_notes db keys).
|
||||
|
||||
## Backend
|
||||
- Drop the `contacts` + `contact_notes` tables, the `tasks.contact_id` column (FK + index), the `contacts_summary` view, and the functions `merge_contacts`, `handle_contact_saved`, `handle_contact_note_created_or_updated` outright — plus their triggers/policies/grants and the `contacts_id_seq` / `"contactNotes_id_seq"` grants.
|
||||
- **Orphaned helpers:** `get_avatar_for_email` (only called by `handle_contact_saved`) and `lowercase_email_jsonb` (only wired to the contact email trigger) — remove them + grants.
|
||||
- `activity_log` view carries the contact pattern (a `contact` + `contact_note` column on every branch **and** two dedicated branches `contact.created`/`contactNote.created`) — same shape as deals.
|
||||
- **Edge functions (deepest of the five):** delete the whole `merge_contacts/` function (+ its `[functions.merge_contacts]` block in `config.toml`) and the whole `postmark/` inbound-email function (+ its `[functions.postmark]` block) — this leaves the inbound-email settings UI in `ProfilePage`/`SettingsPageMobile` and the `POSTMARK_*`/`VITE_INBOUND_EMAIL` env vars hollow; **flag that to the user**. In `_shared/db.ts` drop `ContactsTable`/`ContactNotesTable`, the `tasks.contact_id` field, and their `Database` registrations. In `mcp/`: `contacts` (not tasks) drives `CRM_BASE_URL` — the `display_task_list` `contact_name`/`contact_id` schema fields and `taskListUi.ts`'s contact-link rendering exist only to deep-link task→contact, so removing them makes `CRM_BASE_URL` (in `mcp/index.ts`, the `__CRM_BASE_URL__` HTML placeholder, and `supabase/functions/.env`) dead — drop it. Rewrite the `contacts`/`contacts_summary` mentions in the `get_schema`/`query`/`mutate` prompt examples and swap the table name in `mcp/validateSql.test.ts` fixtures for a surviving table.
|
||||
|
||||
## grep — benign matches to leave (intentional)
|
||||
- `resources.companies.field_categories.contact` (a company "Company info" section label in `CompanyInputs.tsx`/`CompanyAside.tsx`, not the contacts resource), the `resource="contacts"` JSDoc examples in `misc/CreateSheet.tsx`/`EditSheet.tsx`, and the now-unused `resources.contacts.*` i18n keys (optional/large — flag, don't force). `supabase/migrations/` keeps historical references — never edit them.
|
||||
19
.claude/skills/delete-initial-resource/deals.md
Normal file
19
.claude/skills/delete-initial-resource/deals.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Deleting `deals`
|
||||
|
||||
`deals` owns the `deals` + `deal_notes` tables but is woven into **two shared subsystems** (notes, activity log), adds an aggregated `nb_deals` to `companies_summary`, ships **three** config props plus a non-eponymous one, and has a custom dataProvider method — so it fans out almost as wide as a column removal.
|
||||
|
||||
## Frontend specifics
|
||||
- **Shared subsystems** — narrow the notes union to `"contacts"` and the activity-log union to drop `"deal"`; prune the `=== "deals"`/`deal_id`/`DealNote` branches (the `=== "contacts"` checks stay valid). Delete `ActivityLogDealCreated.tsx` + `ActivityLogDealNoteCreated.tsx` outright; drop `getNewDealsAndNotes` in `providers/commons/activity.ts` and `DEAL_CREATED`/`DEAL_NOTE_CREATED` + `ActivityDeal*` in `consts.ts`/`types.ts`. Drop the `"deals"` reference test in `NoteInputs.test.tsx`.
|
||||
- **Config props** — `dealStages`/`dealCategories`/`dealPipelineStatuses` **plus `currency`**, which is deal-scoped but NOT named after the resource (it lived in the Deals `<Card>`; readers are `DealsChart`/`DealsPipeline`/`CompanyShow`'s deal iterator). Removing `currency` is a judgement call — flag it. The Deals `<Card>` also carries a `validateItemsInUse` helper — exported, unit-tested in `SettingsPage.test.ts` (delete that test file outright), backed by a `crm.settings.validation` i18n block; delete all three.
|
||||
- **Custom method** — `unarchiveDeal(deal)` in **both** providers (drops from `CrmDataProvider` automatically via the supabase provider's return type).
|
||||
- **Wrapper components** outside the folder, delete outright: `dashboard/DealsChart.tsx`, `dashboard/DealsPipeline.tsx`. `Dashboard.tsx` keeps both an import **and** a `{totalDeal ? <DealsChart/> : null}` JSX site — remove both. `LatestNotes.tsx` fetches `deal_notes` + renders `<Deal/>`.
|
||||
- **`nb_deals` readers** (invisible to `\bdeals?\b` grep): `CompanyShow.tsx` (deals tab), `CompanyCard.tsx` (badge). `ContactMergeButton.tsx` shows a `dealsCount` preview.
|
||||
- i18n: `resources.deals`, `nb_deals`, activity/dashboard/`settings.deals`/`settings.validation` keys; `i18nProvider.test.ts` asserts a `resources.deals.*` key.
|
||||
|
||||
## Backend
|
||||
- Drop the `deals` + `deal_notes` tables, grants, policies, sequences. Update `companies_summary` (drop `count(distinct d.id) as nb_deals` + the `left join public.deals`).
|
||||
- `activity_log` view: `deals` contributed a `deal` + `deal_note` column to **every** branch **and** two dedicated branches (`deal.created`, `dealNote.created`) — remove both, keep column lists aligned, and drop the `deal_note → dealNote` rename in the supabase provider's `getList("activity_log")`.
|
||||
- **MCP:** **no** dedicated MCP App — only the `DealsTable` Kysely interface in `_shared/db.ts`, the deal-reassignment step in `merge_contacts/index.ts` (its removal leaves the `sql` import unused — drop it), and deal mentions in the `query`/`mutate` **prompt-text examples** in `mcp/index.ts` (rewrite the examples, don't just delete lines).
|
||||
|
||||
## grep — benign matches to leave
|
||||
- `deals` matches `ideal` (an English comment in `RelativeDate.tsx`) and the French `affaires`/`Chiffre d'affaires` (= revenue, not a deal, in `frenchCrmMessages.ts`) — filter them out and don't blind-delete French lines.
|
||||
350
.claude/skills/delete-initial-resource/delete-initial-resource.ts
Executable file
350
.claude/skills/delete-initial-resource/delete-initial-resource.ts
Executable file
@@ -0,0 +1,350 @@
|
||||
// Run with `node .claude/skills/delete-initial-resource/delete-initial-resource.ts`
|
||||
// (Node >= 22.18 strips TypeScript types natively; on older Node use `npx -y tsx <...>`).
|
||||
// `process` is declared locally because this standalone script lives outside the
|
||||
// project's tsconfig and so doesn't pick up the global @types/node.
|
||||
declare const process: {
|
||||
cwd(): string;
|
||||
argv: string[];
|
||||
exit(code?: number): never;
|
||||
};
|
||||
import { existsSync } from "node:fs";
|
||||
import { rm } from "node:fs/promises";
|
||||
|
||||
// ================================ CONSTANTS ================================
|
||||
const initialResource = [
|
||||
"contacts",
|
||||
"companies",
|
||||
"deals",
|
||||
"tags",
|
||||
"tasks",
|
||||
] as const;
|
||||
const dependentFiles: ResourceFiles = {
|
||||
companies: [
|
||||
"src/components/atomic-crm/dataImport/useCompanyImport.ts",
|
||||
"src/components/atomic-crm/dataImport/companies_sample.csv",
|
||||
"src/components/atomic-crm/dataImport/useCompanyResolver.ts",
|
||||
"src/components/atomic-crm/dataImport/useImportableResources.ts",
|
||||
"src/components/atomic-crm/types.ts",
|
||||
"src/components/atomic-crm/consts.ts",
|
||||
"src/components/atomic-crm/activity/ActivityLogCompanyCreated.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLog.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogContext.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogIterator.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogContactCreated.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogContactNoteCreated.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogDealCreated.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogDealNoteCreated.tsx",
|
||||
"src/components/atomic-crm/providers/commons/activity.ts",
|
||||
"src/components/atomic-crm/root/defaultConfiguration.ts",
|
||||
"src/components/atomic-crm/root/ConfigurationContext.tsx",
|
||||
"src/components/atomic-crm/settings/SettingsPage.tsx",
|
||||
"src/App.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactInputs.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactShow.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactListContent.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactList.tsx",
|
||||
"src/components/atomic-crm/contacts/contactModel.ts",
|
||||
"src/components/atomic-crm/contacts/ExportVCardButton.tsx",
|
||||
"src/components/atomic-crm/contacts/useContactImport.tsx",
|
||||
"src/components/atomic-crm/contacts/Avatar.tsx",
|
||||
"src/components/atomic-crm/contacts/contacts_export.csv",
|
||||
"src/components/atomic-crm/deals/DealInputs.tsx",
|
||||
"src/components/atomic-crm/deals/DealCard.tsx",
|
||||
"src/components/atomic-crm/deals/DealShow.tsx",
|
||||
"src/components/atomic-crm/deals/DealEdit.tsx",
|
||||
"src/components/atomic-crm/deals/DealList.tsx",
|
||||
"src/components/atomic-crm/deals/OnlyMineInput.tsx",
|
||||
"src/components/atomic-crm/deals/ContactList.tsx",
|
||||
"src/components/atomic-crm/notes/Note.tsx",
|
||||
"src/components/atomic-crm/dashboard/HotContacts.tsx",
|
||||
"src/components/atomic-crm/dashboard/DealsPipeline.tsx",
|
||||
"src/components/atomic-crm/misc/ContactOption.tsx",
|
||||
"src/components/atomic-crm/misc/useImportFromJson.ts",
|
||||
"src/components/atomic-crm/misc/ImportPage.tsx",
|
||||
"src/components/atomic-crm/misc/import-sample.json",
|
||||
"src/components/atomic-crm/providers/supabase/dataProvider.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataProvider.ts",
|
||||
"src/components/atomic-crm/providers/commons/getCompanyAvatar.ts",
|
||||
"src/components/atomic-crm/providers/commons/mergeContacts.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/companies.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/contacts.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/deals.ts",
|
||||
"src/components/atomic-crm/providers/commons/englishCrmMessages.ts",
|
||||
"src/components/atomic-crm/providers/commons/frenchCrmMessages.ts",
|
||||
"src/components/atomic-crm/layout/Header.tsx",
|
||||
"src/components/atomic-crm/layout/MobileNavigation.tsx",
|
||||
"src/test/StoryWrapper.tsx",
|
||||
"test-data/contacts.csv",
|
||||
],
|
||||
contacts: [
|
||||
"src/components/atomic-crm/dataImport/useImportableResources.ts",
|
||||
"src/components/atomic-crm/dataImport/useCompanyResolver.ts",
|
||||
"src/components/atomic-crm/types.ts",
|
||||
"src/components/atomic-crm/consts.ts",
|
||||
"src/components/atomic-crm/activity/ActivityLogContactCreated.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogContactNoteCreated.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogIterator.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLog.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogContext.tsx",
|
||||
"src/components/atomic-crm/providers/commons/activity.ts",
|
||||
"src/components/atomic-crm/dashboard/HotContacts.tsx",
|
||||
"src/components/atomic-crm/dashboard/DashboardStepper.tsx",
|
||||
"src/components/atomic-crm/dashboard/Dashboard.tsx",
|
||||
"src/components/atomic-crm/dashboard/MobileDashboard.tsx",
|
||||
"src/components/atomic-crm/dashboard/LatestNotes.tsx",
|
||||
"src/components/atomic-crm/dashboard/TasksList.tsx",
|
||||
"src/components/atomic-crm/companies/CompanyShow.tsx",
|
||||
"src/components/atomic-crm/companies/CompanyCard.tsx",
|
||||
"src/components/atomic-crm/companies/CompanyList.tsx",
|
||||
"src/components/atomic-crm/notes/foreignKeyMapping.ts",
|
||||
"src/components/atomic-crm/notes/NoteInputs.tsx",
|
||||
"src/components/atomic-crm/notes/NoteInputs.test.tsx",
|
||||
"src/components/atomic-crm/notes/NoteCreate.tsx",
|
||||
"src/components/atomic-crm/notes/NotesIterator.tsx",
|
||||
"src/components/atomic-crm/notes/Note.tsx",
|
||||
"src/components/atomic-crm/notes/NoteAttachments.tsx",
|
||||
"src/components/atomic-crm/notes/index.ts",
|
||||
"src/components/atomic-crm/notes/NoteCreateSheet.tsx",
|
||||
"src/components/atomic-crm/notes/NoteEditSheet.tsx",
|
||||
"src/components/atomic-crm/notes/NoteShowPage.tsx",
|
||||
"src/components/atomic-crm/notes/NotesIteratorMobile.tsx",
|
||||
"src/components/atomic-crm/notes/NoteInputsMobile.tsx",
|
||||
"src/components/atomic-crm/tasks/AddTask.tsx",
|
||||
"src/components/atomic-crm/tasks/Task.tsx",
|
||||
"src/components/atomic-crm/tasks/TaskCreateSheet.tsx",
|
||||
"src/components/atomic-crm/tasks/TaskCreateSheet.stories.tsx",
|
||||
"src/components/atomic-crm/tasks/TaskCreateSheet.test.tsx",
|
||||
"src/components/atomic-crm/tasks/TaskEditSheet.tsx",
|
||||
"src/components/atomic-crm/tasks/TaskFormContent.tsx",
|
||||
"src/components/atomic-crm/tasks/TasksIterator.tsx",
|
||||
"src/components/atomic-crm/tasks/TasksListFilter.tsx",
|
||||
"src/components/atomic-crm/tasks/TasksListFilter.test.tsx",
|
||||
"src/components/atomic-crm/tasks/TasksListByDueDate.tsx",
|
||||
"src/components/atomic-crm/deals/DealInputs.tsx",
|
||||
"src/components/atomic-crm/deals/DealShow.tsx",
|
||||
"src/components/atomic-crm/deals/ContactList.tsx",
|
||||
"src/components/atomic-crm/deals/DealEmpty.tsx",
|
||||
"src/components/atomic-crm/misc/ContactOption.tsx",
|
||||
"src/components/atomic-crm/misc/useImportFromJson.ts",
|
||||
"src/components/atomic-crm/misc/ImportPage.tsx",
|
||||
"src/components/atomic-crm/misc/import-sample.json",
|
||||
"src/components/atomic-crm/providers/commons/mergeContacts.ts",
|
||||
"src/components/atomic-crm/providers/commons/getContactAvatar.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataProvider.ts",
|
||||
"src/components/atomic-crm/providers/supabase/dataProvider.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/contacts.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/contactNotes.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/finalize.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/deals.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/tasks.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/companies.ts",
|
||||
"src/components/atomic-crm/providers/commons/englishCrmMessages.ts",
|
||||
"src/components/atomic-crm/providers/commons/frenchCrmMessages.ts",
|
||||
"src/components/atomic-crm/layout/Header.tsx",
|
||||
"src/components/atomic-crm/layout/MobileNavigation.tsx",
|
||||
"src/components/atomic-crm/login/SignupPage.tsx",
|
||||
"src/test/StoryWrapper.tsx",
|
||||
],
|
||||
deals: [
|
||||
"src/components/atomic-crm/dataImport/useDealImport.ts",
|
||||
"src/components/atomic-crm/dataImport/deals_sample.csv",
|
||||
"src/components/atomic-crm/dataImport/useImportableResources.ts",
|
||||
"src/components/atomic-crm/types.ts",
|
||||
"src/components/atomic-crm/consts.ts",
|
||||
"src/components/atomic-crm/dashboard/DealsChart.tsx",
|
||||
"src/components/atomic-crm/dashboard/DealsPipeline.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogDealCreated.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogDealNoteCreated.tsx",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/deals.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/dealNotes.ts",
|
||||
"src/components/atomic-crm/notes/foreignKeyMapping.ts",
|
||||
"src/components/atomic-crm/notes/NoteInputs.tsx",
|
||||
"src/components/atomic-crm/notes/NoteCreate.tsx",
|
||||
"src/components/atomic-crm/notes/Note.tsx",
|
||||
"src/components/atomic-crm/notes/NoteAttachments.tsx",
|
||||
"src/components/atomic-crm/notes/NotesIterator.tsx",
|
||||
"src/components/atomic-crm/notes/NoteInputs.test.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogIterator.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLog.tsx",
|
||||
"src/components/atomic-crm/activity/ActivityLogContext.tsx",
|
||||
"src/components/atomic-crm/dashboard/Dashboard.tsx",
|
||||
"src/components/atomic-crm/dashboard/LatestNotes.tsx",
|
||||
"src/components/atomic-crm/providers/commons/activity.ts",
|
||||
"src/components/atomic-crm/providers/commons/mergeContacts.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataProvider.ts",
|
||||
"src/components/atomic-crm/providers/supabase/dataProvider.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/companies.ts",
|
||||
"src/components/atomic-crm/providers/commons/englishCrmMessages.ts",
|
||||
"src/components/atomic-crm/providers/commons/frenchCrmMessages.ts",
|
||||
"src/components/atomic-crm/providers/commons/i18nProvider.test.ts",
|
||||
"src/components/atomic-crm/root/defaultConfiguration.ts",
|
||||
"src/components/atomic-crm/root/ConfigurationContext.tsx",
|
||||
"src/components/atomic-crm/settings/SettingsPage.tsx",
|
||||
"src/components/atomic-crm/settings/SettingsPage.test.ts",
|
||||
"src/App.tsx",
|
||||
"src/components/atomic-crm/companies/CompanyShow.tsx",
|
||||
"src/components/atomic-crm/companies/CompanyCard.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactMergeButton.tsx",
|
||||
"src/components/atomic-crm/layout/Header.tsx",
|
||||
"src/components/atomic-crm/layout/MobileNavigation.tsx",
|
||||
"src/test/StoryWrapper.tsx",
|
||||
],
|
||||
tags: [
|
||||
"src/components/atomic-crm/types.ts",
|
||||
"src/components/atomic-crm/contacts/TagsList.tsx",
|
||||
"src/components/atomic-crm/contacts/TagsListEdit.tsx",
|
||||
"src/components/atomic-crm/contacts/BulkTagButton.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactShow.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactAside.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactListContent.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactList.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactListFilter.tsx",
|
||||
"src/components/atomic-crm/contacts/contactModel.ts",
|
||||
"src/components/atomic-crm/contacts/useContactImport.tsx",
|
||||
"src/components/atomic-crm/companies/CompanyShow.tsx",
|
||||
"src/components/atomic-crm/misc/useImportFromJson.ts",
|
||||
"src/components/atomic-crm/providers/commons/mergeContacts.ts",
|
||||
"src/components/atomic-crm/providers/commons/englishCrmMessages.ts",
|
||||
"src/components/atomic-crm/providers/commons/frenchCrmMessages.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/contacts.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts",
|
||||
"src/components/atomic-crm/contacts/ContactList.stories.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactList.test.tsx",
|
||||
"src/test/StoryWrapper.tsx",
|
||||
"src/components/atomic-crm/contacts/contacts_export.csv",
|
||||
"test-data/contacts.csv",
|
||||
],
|
||||
tasks: [
|
||||
"src/components/atomic-crm/types.ts",
|
||||
"src/components/atomic-crm/dashboard/TasksList.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactTasksList.tsx",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/tasks.ts",
|
||||
"src/components/atomic-crm/dashboard/Dashboard.tsx",
|
||||
"src/components/atomic-crm/layout/MobileNavigation.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactShow.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactShow.test.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactAside.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactListContent.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactListFilter.tsx",
|
||||
"src/components/atomic-crm/contacts/ContactMergeButton.tsx",
|
||||
"src/components/atomic-crm/companies/CompanyShow.tsx",
|
||||
"src/components/atomic-crm/misc/useImportFromJson.ts",
|
||||
"src/components/atomic-crm/misc/ImportPage.tsx",
|
||||
"src/components/atomic-crm/misc/import-sample.json",
|
||||
"src/components/atomic-crm/providers/commons/mergeContacts.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataProvider.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/index.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/types.ts",
|
||||
"src/components/atomic-crm/providers/fakerest/dataGenerator/contacts.ts",
|
||||
"src/test/StoryWrapper.tsx",
|
||||
"src/components/atomic-crm/providers/commons/englishCrmMessages.ts",
|
||||
"src/components/atomic-crm/providers/commons/frenchCrmMessages.ts",
|
||||
"src/components/atomic-crm/root/defaultConfiguration.ts",
|
||||
"src/components/atomic-crm/root/ConfigurationContext.tsx",
|
||||
"src/components/atomic-crm/settings/SettingsPage.tsx",
|
||||
"src/App.tsx",
|
||||
],
|
||||
} as const;
|
||||
const sharedDependentFiles: string[] = [
|
||||
"src/components/atomic-crm/root/CRM.tsx",
|
||||
];
|
||||
const resourceFilesPath = "src/components/atomic-crm" as const;
|
||||
|
||||
// ================================ TYPES ================================
|
||||
type InitialResource = (typeof initialResource)[number];
|
||||
type DependentFile = string;
|
||||
type ResourceFiles = Record<InitialResource, DependentFile[]>;
|
||||
|
||||
// ================================ FUNCTIONS ================================
|
||||
const main = async () => {
|
||||
const resourcesToDelete = getResources();
|
||||
const basePath = process.cwd();
|
||||
for (const resource of resourcesToDelete) {
|
||||
await deleteIsolatedFiles(resource, basePath);
|
||||
}
|
||||
returnDependentFiles(resourcesToDelete);
|
||||
};
|
||||
|
||||
// Get the resources to delete from the command line arguments (one or more),
|
||||
// validate each one, and de-duplicate.
|
||||
const getResources = (): InitialResource[] => {
|
||||
const args = process.argv.slice(2);
|
||||
if (args.length === 0) {
|
||||
console.error(
|
||||
"Please provide at least one resource to delete (you may pass several).",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const invalid = args.filter(
|
||||
(resource) => !initialResource.includes(resource as InitialResource),
|
||||
);
|
||||
if (invalid.length > 0) {
|
||||
console.error(
|
||||
`Resource(s) ${invalid
|
||||
.map((resource) => `"${resource}"`)
|
||||
.join(", ")} do not exist. Valid resources: ${initialResource.join(
|
||||
", ",
|
||||
)}.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return [...new Set(args)] as InitialResource[];
|
||||
};
|
||||
|
||||
// Delete the resource folder and its files.
|
||||
const deleteIsolatedFiles = async (
|
||||
resource: InitialResource,
|
||||
basePath: string,
|
||||
) => {
|
||||
const folderPath = `${basePath}/${resourceFilesPath}/${resource}`;
|
||||
|
||||
if (!existsSync(folderPath)) {
|
||||
console.error(
|
||||
`Folder for resource "${resource}" does not exist. Skipping deletion of isolated files.`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await rm(folderPath, { recursive: true, force: true });
|
||||
};
|
||||
|
||||
// Return to Claude all the files dependent on the deleted resource(s).
|
||||
// When several resources are deleted at once, merge and de-duplicate their
|
||||
// dependent-file lists, then drop any file that lives inside a folder we just
|
||||
// deleted (e.g. `companies/CompanyShow.tsx` is a dependent of `contacts`, but
|
||||
// it is gone once `companies` is deleted too).
|
||||
const returnDependentFiles = (resources: InitialResource[]) => {
|
||||
const deletedFolders = resources.map(
|
||||
(resource) => `${resourceFilesPath}/${resource}/`,
|
||||
);
|
||||
|
||||
const merged = new Set<DependentFile>([
|
||||
...resources.flatMap((resource) => dependentFiles[resource]),
|
||||
...sharedDependentFiles,
|
||||
]);
|
||||
|
||||
const allDependentFilesToReturn = [...merged]
|
||||
.filter((file) => !deletedFolders.some((folder) => file.startsWith(folder)))
|
||||
.sort();
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(
|
||||
`Dependent files for resource(s) ${resources
|
||||
.map((resource) => `"${resource}"`)
|
||||
.join(", ")}:\n`,
|
||||
allDependentFilesToReturn,
|
||||
);
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
// ================================ MAIN CALL ================================
|
||||
await main();
|
||||
14
.claude/skills/delete-initial-resource/tags.md
Normal file
14
.claude/skills/delete-initial-resource/tags.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Deleting `tags`
|
||||
|
||||
The simplest of the five: `tags` is a **field** (`number[]`) on `Contact` plus a small standalone `tags` folder. No shared subsystem, no config prop, no aggregated column.
|
||||
|
||||
## Frontend specifics
|
||||
- Tag-only components under `contacts/` — delete outright: `TagsList.tsx`, `TagsListEdit.tsx`, `BulkTagButton.tsx`. Clean their consumers (`ContactShow`, `ContactAside`, `ContactListContent`, `ContactList`, `ContactListFilter`).
|
||||
- The `contacts.tags` field lives in `contactModel.ts`, `useContactImport.tsx`, `providers/commons/mergeContacts.ts`, the FakeRest generators (`dataGenerator/contacts.ts` + `index.ts` + `types.ts`), the stories/tests (`ContactList.stories.tsx`, `ContactList.test.tsx`, `StoryWrapper.tsx`), and the sample CSVs (`contacts_export.csv`, `test-data/contacts.csv`).
|
||||
- i18n: drop the `bulk_tag` block, `filters.tags`, and the `tags` resource block (both catalogs — see SKILL.md).
|
||||
|
||||
## Backend
|
||||
- Drop the `tags` table + its grants/policies/sequence. `merge_contacts` in `02_functions.sql` merges `tags` — drop those lines + the now-unused local var, and renumber step comments.
|
||||
|
||||
## grep — benign matches to leave
|
||||
- `tags` matches `stage` — filter it out (`grep -viE "stage"`).
|
||||
13
.claude/skills/delete-initial-resource/tasks.md
Normal file
13
.claude/skills/delete-initial-resource/tasks.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Deleting `tasks`
|
||||
|
||||
`tasks` looks standalone (it owns the `tasks` folder + table) but it **adds an aggregated `nb_tasks` column to `contacts_summary`** (which lands on the `Contact` type) and ships a **`taskTypes` config prop** — so it fans out almost like a column removal.
|
||||
|
||||
## Frontend specifics
|
||||
- **`nb_tasks` readers:** `CompanyShow.tsx`, `ContactListContent.tsx`, `ContactShow.tsx` (+ test), `ContactListFilter.tsx` (a `nb_tasks@gt` filter), `ContactMergeButton.tsx`, and the FakeRest provider/generators (which increment/decrement it). **Invisible to `\btasks?\b` grep** — grep `nb_tasks` separately.
|
||||
- **`taskTypes` config prop** — see SKILL.md "Shapes a resource takes". The Tasks `<Card>` has no `validateItemsInUse`.
|
||||
- **Wrapper components** outside the folder, delete outright: `dashboard/TasksList.tsx`, `contacts/ContactTasksList.tsx`.
|
||||
- i18n: drop the `tasks` resource block, `task_count`, `upcoming_tasks`, `filters.tasks`, `settings.tasks`, and the import description (both catalogs — see SKILL.md).
|
||||
|
||||
## Backend
|
||||
- Drop the `tasks` table + grants/policies/sequence.
|
||||
- **MCP edge function:** `mcp/taskListUi.ts` is a ~13 KB guest-HTML "MCP App" — delete outright. Remove the `display_task_list` + `complete_task` tools and their prompt examples in `mcp/index.ts`, and the `tasks` example in `mcp/validateSql.test.ts`. (Note: the `CRM_BASE_URL` env var is driven by the task→contact deep-link, which is really a `contacts` concern — see [`contacts.md`](contacts.md); it only goes dead if contacts is deleted too.)
|
||||
Reference in New Issue
Block a user