Initial commit: webmail client (M0/M1)
Three-pane webmail client. - Backend: FastAPI + async SQLAlchemy + arq worker (Postgres/Redis), IMAP/SMTP sync, threading, search, drafts/outbox with undo, attachments, 2FA. - Frontend: React 18 + Vite + TypeScript + TanStack Query + Tailwind; three-pane UI with rich-text composer, light/dark themes, resizable panels. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
# Open Questions
|
||||
|
||||
Per the setup rules, deviations and unresolved decisions are recorded here rather
|
||||
than guessed silently.
|
||||
|
||||
## OQ-1 — Meilisearch container healthcheck reports `unhealthy` (IPv6 `localhost`)
|
||||
|
||||
**Status:** resolved — approved deviation applied. The healthcheck URL in
|
||||
`docker/docker-compose.yml` was changed from `localhost` to `127.0.0.1`; the
|
||||
container now reports `healthy`.
|
||||
|
||||
**Symptom:** `docker compose ps` shows `meilisearch` as `unhealthy`, even though the
|
||||
service is fully up and reachable from the host (`curl http://127.0.0.1:7700/health`
|
||||
returns `{"status":"available"}`).
|
||||
|
||||
**Root cause:** the spec's healthcheck is
|
||||
`wget -qO- http://localhost:7700/health | grep -q available`. Inside the
|
||||
`getmeili/meilisearch:v1.8` container, `/etc/hosts` maps `localhost` to both
|
||||
`127.0.0.1` and `::1`, and busybox `wget` tries the IPv6 `::1` first. Meilisearch
|
||||
listens IPv4-only on `0.0.0.0:7700` (`MEILI_HTTP_ADDR=0.0.0.0:7700`), so the `::1`
|
||||
connection is refused and the healthcheck fails. `wget http://127.0.0.1:7700/health`
|
||||
from inside the container succeeds.
|
||||
|
||||
**Impact:** cosmetic for direct use (the service works), but real for automation:
|
||||
`docker compose up --wait` (used by the `reset-db` Make target) and the
|
||||
"all must be healthy" verification step will hang/fail on this host.
|
||||
|
||||
**Proposed minimal fix (one word):** change the healthcheck URL from `localhost`
|
||||
to `127.0.0.1`:
|
||||
`test: ["CMD-SHELL", "wget -qO- http://127.0.0.1:7700/health | grep -q available"]`.
|
||||
This is a deviation from the frozen setup doc, so it is left for explicit approval.
|
||||
|
||||
## OQ-5 — Relative BLOB_ROOT breaks across processes with different CWDs
|
||||
|
||||
**Status:** resolved in dev by using an absolute path.
|
||||
|
||||
`BLOB_ROOT=./var/blobs` (relative) resolves against each process's working
|
||||
directory. Per the dev-setup doc the API runs from the repo root (`uvicorn
|
||||
--app-dir backend`) while the worker runs "from backend/", so blobs written by the
|
||||
worker land in `backend/var/blobs` but the API reads `./var/blobs` at the repo
|
||||
root — a different, empty directory. This only surfaced once the API began reading
|
||||
blobs (M1 re-sanitize-on-render / attachments); before that the API only served
|
||||
sanitized HTML from Postgres.
|
||||
|
||||
**Fix:** `.env.dev` now sets an absolute `BLOB_ROOT`. Additionally
|
||||
`GET /messages/{id}/html` falls back to the stored (possibly stale) sanitized HTML
|
||||
if a blob is unreadable, so a path/permission issue degrades gracefully instead of
|
||||
500-ing. `.env.example` keeps the relative default with a note; production should
|
||||
always use an absolute path or run both processes from the same CWD.
|
||||
|
||||
## OQ-4 — Duplicate Message-ID / bulk-sender over-merging (threading)
|
||||
|
||||
**Status:** resolved with two refinements (both consistent with §7.1's stated intent).
|
||||
|
||||
**Symptom (found while testing):** home.pl's welcome mailer reuses one static
|
||||
`Message-ID` (and `Date`) for every recipient. Two welcome mails — one per test
|
||||
account, 104 days apart, byte-identical — collapsed into a single thread.
|
||||
|
||||
**Cause & fixes:**
|
||||
1. **Duplicate Message-ID (§7.1, was only half-implemented).** The indexer now
|
||||
synthesizes a non-authoritative id when a Message-ID is already present in a
|
||||
**different account** (a separate delivery). A **same-account** duplicate keeps
|
||||
the real id, so Gmail-style INBOX + All Mail copies of one message still thread
|
||||
together. Implemented in `app/indexer/indexer.py::_resolve_message_id`; new
|
||||
index `ix_messages_user_msgid` (migration 0002).
|
||||
2. **Subject fallback (§7.3 refinement).** The literal rule ("same subject + within
|
||||
7 days + shares ≥1 participant") over-merges bulk mail, because the shared
|
||||
participant can be just the common sender. Subject joining now keys on the
|
||||
**incoming message's recipients (To/Cc)** overlapping the thread — a genuine
|
||||
reply is always addressed to someone in the thread, a bulk blast to another
|
||||
account is not. `app/threading_/threader.py::_find_by_subject`.
|
||||
|
||||
Both are minor tightenings of the spec's anti-over-merge intent, not behavior the
|
||||
spec forbids. Covered by `tests/indexer/test_duplicate_msgid.py` and updated
|
||||
threading vectors.
|
||||
|
||||
## OQ-3 — `__Host-` session cookie vs. plain-HTTP dev
|
||||
|
||||
**Status:** resolved with a documented dev fallback.
|
||||
|
||||
SPEC §6.1/§10.3 mandate the `__Host-session` cookie prefix, which the browser
|
||||
only accepts when the cookie is `Secure` **and** sent over HTTPS. The dev setup
|
||||
(`dev-environment-setup.md`) serves the API over plain HTTP (uvicorn :8000 behind
|
||||
the Vite HTTP proxy), so a `__Host-`/`Secure` cookie would be silently dropped and
|
||||
login could never work in dev.
|
||||
|
||||
**Decision:** cookie naming/flags are derived per-request from the scheme
|
||||
(`app/identity/cookies.py`): over HTTPS we emit the spec-mandated
|
||||
`__Host-session` + `Secure`; over plain HTTP (dev only) we fall back to `session`
|
||||
without `Secure`. Production (behind TLS) is fully spec-compliant. `SameSite=Lax`,
|
||||
`HttpOnly`, `Path=/` always. The CSRF cookie follows the same scheme rule but is
|
||||
readable by JS (double-submit, SPEC §10.3).
|
||||
|
||||
## OQ-2 — Repository was not a git repo (resolved)
|
||||
|
||||
**Status:** resolved.
|
||||
|
||||
The setup doc's conda `activate.d` hook locates `.env.dev` via
|
||||
`git rev-parse --show-toplevel`, and the overall workflow (commit-per-increment,
|
||||
per-plugin alembic branches) assumes git. The working directory was not a git repo,
|
||||
so `.env.dev` did not auto-load on `conda activate`. Ran `git init` (branch `main`);
|
||||
the hook now loads the env correctly. No spec deviation — the doc already assumes git.
|
||||
Reference in New Issue
Block a user