The /api/pb/records/[id] proxy had an allowlist of editable fields that
was written before the 2026-08-18 onboarding field additions. The new
fields were silently dropped before reaching PocketBase, so the modal
appeared to save successfully but the data never landed in PB.
Added to COMPANY_EDIT_FIELDS:
physical_address_line_1/2, physical_town_city, physical_province,
physical_postal_code, postal_address_line_1/2, postal_town_city,
postal_province, postal_postal_code, company_email, company_cell_phone,
public_officer_name, company_secretary_name, financial_year_end_month,
authorised_shares
Added to DIRECTOR_EDIT_FIELDS:
residential_address_line_1/2, residential_town_city, residential_province,
residential_postal_code, postal_address_line_1/2, postal_town_city,
postal_province, postal_postal_code, date_of_birth
End-to-end tested via proxy PATCH — fields now land in PB.
PB: 3 new fields on ysterbaard_director_appointments
- proceed_anyway (bool) — true if user proceeded with missing emails
- missing_emails (text) — comma-separated keys
- missing_emails_detail (text) — JSON per-recipient validation
App:
- POST /api/appointments: builds recipient report (initial +
distro); returns 422 + report if proceed_anyway is missing
and any recipient is missing/invalid. preview=1 returns the
report without sending.
- PATCH /api/appointments/[id]: tightened idempotency guard to
also short-circuit on signature_received (not just
mail_sent_to_all). Prevents double-send on double-click.
- DirectorAppointmentTab: two-step modal — "Validate recipients"
surfaces the report (status dots + per-recipient email),
then Send / Proceed anyway / Fix records first. New
"company_email" gap surfaced before any email leaves.
- lib/format.ts: isValidEmail() shared helper.
End-to-end tested: 422 enforces server-side, preview returns
the report, double-PATCH signs only once.
- New /Volumes/KIOXIA/.hermes/profiles/ysterbaard/scripts/pdfd.js
standalone HTML->PDF daemon on localhost:3070, calls soffice via
execSync (proven working pattern from resignation form watcher.js)
- app/api/appointments/route.ts generates acceptance-letter.pdf via
daemon, attaches via MIME multipart/mixed
- app/api/appointments/[id]/route.ts renders signed-letter.pdf with
embedded signature, attaches to distro mail
- Idempotency guard: skip distro if status is already mail_sent_to_all
A task with status='' caused the Kanban byColumn memo to throw
TypeError: undefined is not an object (out[t.status].push).
Defensive UI: bucket unknown statuses into backlog with a console
warning. Defensive API: reject PATCHes with invalid status values
(including empty string) with a 400. Cleaned up two existing
empty-status rows in PB.
Adds the visible front-end and the worker back-end for the agent
task system. The PocketBase schema, API routes, and ToastProvider
were already in the previous commit; this commit completes the
picture.
UI
- app/tasks/{page,KanbanBoard,TaskCard,CardModal,TaskDetail,api,columns}
Full Kanban with six columns (Backlog, Ready, In Progress,
Blocked, Done, Failed), drag-and-drop between columns, quick-add
chips, type/priority filters, task detail panel with retry/cancel/
delete, 5s polling, transition toasts.
- components/NavBar.tsx: 'Tasks' link in primary nav.
- app/bizportal-2/BizPortal2Explorer.tsx: 'Send to Kanban' button
on the result view — dispatches the current search to the agent
queue as a 'ready' task.
Back-end
- scripts/task_worker.py: PocketBase polling worker with two
lanes. Deterministic handles bizportal_search/reserve, pdf_generate,
email_send. Custom lane writes to /tmp/agent_inbox/ for the
LLM agent to pick up. Atomic claim via PB filter, auto-retry up
to 3x, structured log on each task.
- scripts/bizportal_service.py: bugfix — _is_logged_in was using
a non-CSS selector so session-recovery was a no-op. Also made
the login flow re-runnable mid-session (handles BizPortal's
~20-minute session timeout transparently).
- scripts/init_pb_schema.py: duration_ms no longer capped at 0.
Verified end-to-end:
- CK search via worker: 1 company, 3 directors, ~10s
- ID search via worker: 4 companies, 7 total directors, ~34s
- Auto-retry + status transitions + toast fires
- DirectorAppointmentTab: table + 'Acceptance Letter' modal with company/director dropdowns
- GET/POST /api/appointments: create record, generate HTML letter, convert to PDF via LibreOffice, email to director via himalaya SMTP
- PATCH /api/appointments/[id]: save signature PNG, embed in signed letter PDF, mail to all company directors
- GET /api/appointments/sign/[token]: public token lookup
- /sign/[token]/page.tsx: public signature canvas (mouse + touch)
- ysterbaard_director_appointments PB collection (requires manual schema setup)
- Section 3 'Director Appointment' now active in ResolutionExplorer
- NEXT_PUBLIC_APP_URL added to .env.local
Uncommitted work in progress at the point of starting the Kanban
project. Captures:
- Registration wizard refactor + multi-step routing
- structured physical/postal address fields on companies and directors
- appointments flow (sign + verify)
- document type manager + upload/panel for companies and directors
- status actions, approve button, action/dropdown buttons
- step2 review page
tsconfig.tsbuildinfo is now ignored (build artefact).
Replaces the brittle agent-driven /api/bizportal/search queue with a
deterministic Python FastAPI service that drives BizPortal via a real
headless Chromium. The old flow is untouched.
What this adds:
- scripts/bizportal_service.py — FastAPI + Playwright service with
/search, /reserve, /health endpoints. Persistent login session
via storage_state.json. Locator-based selectors (vs eval JS).
- app/bizportal-2/ — new /bizportal-2 page, synchronous fetch
(no polling), shows round-trip duration, mirrors the existing
bulk-add + link UX.
- app/api/bizportal-2/{search,reserve,health}/route.ts — thin
proxies to the Python sidecar.
- scripts/README.md, .env.example, .gitignore, requirements.txt.
- .gitignore: ignore tsconfig.tsbuildinfo (build artefact).
Wiring:
- components/NavBar.tsx: 'BizPortal 2.0' nav entry with TEST badge.
- app/bizportal/page.tsx: 'Try BizPortal 2.0' action link in
header so the new option is discoverable from the existing page.
Run: cd scripts && source .venv/bin/activate && uvicorn
bizportal_service:app --port 7001