Two structural changes, no behavior change. Every import in the affected files was already on the @/ alias, so this is path rewrites and nothing else — 108 insertions against 108 deletions.
web/src/lib had become the dumping ground
85 flat modules, 42% of the web source, about 12,800 lines, with smime/ as its only subdirectory. The tell was that a naming prefix had taken over a directory's job: eight adminX.ts files sat next to each other because alphabetical order put them there, not because anything declared them a group.
Tests move with their modules into lib/admin/__tests__/ and lib/calendar/__tests__/, matching what views/ already does. describeRules.test.ts stays in lib/__tests__/ — it asserts that sieve's describeRule and recurrence's agree, so it belongs to neither group.
Two membership calls worth checking, because the filenames mislead:
recurrence.ts moved in and archiveDate.ts stayed out, which is the opposite of what the names suggest. archiveDate picks the Archive/2026/09 mailbox for a message — that is mail. recurrence reads JSCalendarRecurrenceRule.
schedule.ts is scheduled send, so it stays. birthdays.ts is left alone on purpose: it is read off the contact cards and only rendered by the calendar, so it genuinely belongs to whichever of the two you ask, and moving it would just relocate the ambiguity.
docs/ held no documentation
It held ten JPEGs and the two headless-Chrome scripts that capture them. The real documentation is a separate site in the ihasmail.org repository, so anyone opening docs/ expecting prose found a screenshot driver.
Images → screenshots/
screenshots.mjs, screenshots-light.mjs → scripts/, alongside the other .mjs tooling, which is where a generator belongs
Renaming docs/ to screenshots/ wholesale would have produced screenshots/screenshots/inbox-dark.jpg. The README's six image links and the usage lines in the capture script's own header are updated; no reference to docs/ remains anywhere in the tree.
Translations: adds none. No user-visible strings — this moves files and rewrites import paths.
Testing:npm run typecheck clean across both workspaces, npm test green (135 web test files / 1,292 tests, 228 server tests), npm run build succeeds. Run locally before pushing, not just left to CI.
Two structural changes, no behavior change. Every import in the affected files was already on the `@/` alias, so this is path rewrites and nothing else — 108 insertions against 108 deletions.
## `web/src/lib` had become the dumping ground
85 flat modules, 42% of the web source, about 12,800 lines, with `smime/` as its only subdirectory. The tell was that a naming prefix had taken over a directory's job: eight `adminX.ts` files sat next to each other because alphabetical order put them there, not because anything declared them a group.
| New folder | Modules |
| --- | --- |
| `lib/admin/` | `adminAccess`, `adminDashboard`, `adminDirectory`, `adminDomains`, `adminGroups`, `adminLists`, `adminRoles`, `adminTenants` |
| `lib/calendar/` | `appointment`, `availabilityWindow`, `eventDrag`, `ics`, `recurrence` |
Tests move with their modules into `lib/admin/__tests__/` and `lib/calendar/__tests__/`, matching what `views/` already does. `describeRules.test.ts` stays in `lib/__tests__/` — it asserts that sieve's `describeRule` and recurrence's agree, so it belongs to neither group.
**Two membership calls worth checking**, because the filenames mislead:
- `recurrence.ts` moved **in** and `archiveDate.ts` stayed **out**, which is the opposite of what the names suggest. `archiveDate` picks the `Archive/2026/09` mailbox for a message — that is mail. `recurrence` reads `JSCalendarRecurrenceRule`.
- `schedule.ts` is scheduled *send*, so it stays. `birthdays.ts` is left alone on purpose: it is read off the contact cards and only rendered by the calendar, so it genuinely belongs to whichever of the two you ask, and moving it would just relocate the ambiguity.
## `docs/` held no documentation
It held ten JPEGs and the two headless-Chrome scripts that capture them. The real documentation is a separate site in the `ihasmail.org` repository, so anyone opening `docs/` expecting prose found a screenshot driver.
- Images → `screenshots/`
- `screenshots.mjs`, `screenshots-light.mjs` → `scripts/`, alongside the other `.mjs` tooling, which is where a generator belongs
Renaming `docs/` to `screenshots/` wholesale would have produced `screenshots/screenshots/inbox-dark.jpg`. The README's six image links and the usage lines in the capture script's own header are updated; no reference to `docs/` remains anywhere in the tree.
**Translations:** adds none. No user-visible strings — this moves files and rewrites import paths.
**Testing:** `npm run typecheck` clean across both workspaces, `npm test` green (135 web test files / 1,292 tests, 228 server tests), `npm run build` succeeds. Run locally before pushing, not just left to CI.
**Merged** 2026-09-15 as coffey-labs/ihasmail@e42c81ab0943
<sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Two structural changes, no behavior change. Every import in the affected files was already on the
@/alias, so this is path rewrites and nothing else — 108 insertions against 108 deletions.web/src/libhad become the dumping ground85 flat modules, 42% of the web source, about 12,800 lines, with
smime/as its only subdirectory. The tell was that a naming prefix had taken over a directory's job: eightadminX.tsfiles sat next to each other because alphabetical order put them there, not because anything declared them a group.lib/admin/adminAccess,adminDashboard,adminDirectory,adminDomains,adminGroups,adminLists,adminRoles,adminTenantslib/calendar/appointment,availabilityWindow,eventDrag,ics,recurrenceTests move with their modules into
lib/admin/__tests__/andlib/calendar/__tests__/, matching whatviews/already does.describeRules.test.tsstays inlib/__tests__/— it asserts that sieve'sdescribeRuleand recurrence's agree, so it belongs to neither group.Two membership calls worth checking, because the filenames mislead:
recurrence.tsmoved in andarchiveDate.tsstayed out, which is the opposite of what the names suggest.archiveDatepicks theArchive/2026/09mailbox for a message — that is mail.recurrencereadsJSCalendarRecurrenceRule.schedule.tsis scheduled send, so it stays.birthdays.tsis left alone on purpose: it is read off the contact cards and only rendered by the calendar, so it genuinely belongs to whichever of the two you ask, and moving it would just relocate the ambiguity.docs/held no documentationIt held ten JPEGs and the two headless-Chrome scripts that capture them. The real documentation is a separate site in the
ihasmail.orgrepository, so anyone openingdocs/expecting prose found a screenshot driver.screenshots/screenshots.mjs,screenshots-light.mjs→scripts/, alongside the other.mjstooling, which is where a generator belongsRenaming
docs/toscreenshots/wholesale would have producedscreenshots/screenshots/inbox-dark.jpg. The README's six image links and the usage lines in the capture script's own header are updated; no reference todocs/remains anywhere in the tree.Translations: adds none. No user-visible strings — this moves files and rewrites import paths.
Testing:
npm run typecheckclean across both workspaces,npm testgreen (135 web test files / 1,292 tests, 228 server tests),npm run buildsucceeds. Run locally before pushing, not just left to CI.Merged 2026-09-15 as coffey-labs/ihasmail@e42c81ab09
Rebuilt from: git history, session transcript.