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.
PB: 15 fields on ysterbaard_companies (financial_year_end_month,
authorised_shares, structured physical/postal address, company_cell_phone,
public_officer_name, company_secretary_name), 11 fields on
ysterbaard_directors (date_of_birth, structured residential/postal address).
App: types widened in lib/types.ts. EditEntityModal rebuilt with full
address sections (line 1, line 2, town/city, SA province dropdown,
postal code), DOB, company-level fields, financial year end month,
authorised shares, public officer, company secretary.
View pages: structured address blocks now show labeled fields
(line 1, line 2, town/city, province, postal code) on both
/companies/[id] and /directors/[id].
- 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
Add a conditional cleanup step to remove the public directory if it does not exist or contains no files. This reduces the final docker image size since Next.js standalone builds automatically inline public assets when they are present, making the copied public directory often redundant.