Files
sales-trainer/docs/HANDOFF.md
Macky c263cc81df feat(ui): app UX/UI redesign + marketing landing site
App (Vue SPA) — 100% presentational, global design-token rebuild in
style.css + polished App/Login/Chat/MyBoard/Personas/Setup/Training.
No logic/API/route/i18n changes (verified: build clean, 4/4 vitest,
independent review PASS 5/5).

website/ — new self-contained static marketing landing site (TH-primary
+ EN toggle, CSS chat mockup, Hallmark theme), verified responsive at
320-768 (no horizontal scroll). Pricing uses placeholders pending owner.

Docs: engineering-log 2026-08-19 + HANDOFF updated.

NOT pushed (push to main auto-deploys to EasyPanel production;
operator approval + JWT_SECRET check required before deploy).
2026-08-19 15:46:21 +07:00

19 KiB
Raw Blame History

HANDOFF — Sales Trainer

Another AI should be able to resume without chat history.

Branch / repo / deploy

  • Repo: ~/Gitea/Sales Trainer/git repo, remote = Gitea.
  • Remote: https://git.moreminimore.com/kunthawat/sales-trainer.git (GITEA_TOKEN via credential helper; never committed).
  • Live deploy: https://moreminimoreapps-saletrainer.ahkhwd.easypanel.host — EasyPanel, auto-redeploys from Gitea on push to main via webhook (≈3 min). Dockerfile builds the frontend in a Node builder stage and serves it from the Python runtime image. LLM vars set in EasyPanel env.

What this is

Corporate multi-user sales-training simulator: admins create persona groups from a product/service/idea (โฟกัส "สินค้า/บริการ/ไอเดีย"), app auto-generates 15 customer personas (5/tier A/B/C) via LLM; trainees pick a scenario (social / พบหน้า), chat 1:1 one-shot to close a sale; a per-turn + final judge LLM evaluates feelings and scores/coaches. Vue SPA + Flask API + filesystem JSON storage (no SQL). i18n TH/EN. No self-registration (admin provisions).

Roles

  • super_admin (bootstrap admin) — full recipe (secret persona fields) + tenant admin.
  • admin — manages groups/users, sees personas with secret fields stripped (IP protection).
  • user (trainee) — trains against personas, own board.

Current state — local code/security gate passed; production-operation gate pending

2026-08-19: app UX/UI redesign (8 files, 100% presentational: global design-token system rebuild in style.css + polish of App/Login/Chat/MyBoard/Personas/Setup/Training) AND a new self-contained marketing landing site (website/index.html + main.css, TH-primary with EN toggle, CSS chat mockup) were built in parallel subagents. Frontend vite build clean, vitest 4/4, independent reviewer PASS (5/5 checks), marketing site verified responsive at 320768 (no horizontal scroll). ALL UNCOMMITTED. Pushing to main auto-deploys to EasyPanel — so the push needs operator approval. Confirm production JWT_SECRET is set before deploying (backend validate_runtime_security fails closed without a ≥32-char secret; that is the existing production-operation gate, not a regression). See docs/engineering-log/2026-08-19-ux-redesign-and-marketing-site.md. 2026-08-18: live-QA UX fixes landed (committed locally, not yet pushed): persona replies can no longer leak raw JSON into the chat bubble; all persona openers are natural greetings (wrong_text cools off only after the seller's first reply); and the chat now auto-closes + auto-summarizes the moment the customer decides (buy / walk / try-to-trial-first), with the manual "สรุปผล"/Finish button removed from the UI. Full backend suite 336 passed (baseline 330 → +6 new tests), frontend build + unit tests clean, independent reviewer passed. See docs/engineering-log/2026-08-18-live-qa-ux-fixes.md. Blocker unchanged: production operation (runtime cutover, real-provider QA, Redis, audit apply) still requires operator approval. 2026-08-16: the S4.4 JSON-importer + error-handler hardening increment (previously staged on top of dbfce9a) was re-verified from a clean requirements.lock.txt venv (330 backend tests, compileall + frontend build clean) and committed + pushed. See docs/engineering-log/2026-08-16-s4-4-importer-errorhandler-commit.md. Blocker is unchanged: production operation (runtime cutover, real-provider QA, Redis, audit apply) still requires operator approval.

The current uncommitted remediation is verified on isolated temporary data: 319 backend tests passed from a clean requirements.lock.txt environment, including 166 focused auth/isolation/export/upload regressions; 4 frontend unit tests and 12 Playwright fixture journeys passed across desktop, 320×568, and 500×768; the production frontend build completed with 1,781 modules and npm audit found 0 vulnerabilities. Compile, AST, diff, dependency, and added-line security checks passed. The checked-in lock is reproducible. The existing local backend/.venv has version drift and pip check reports the pre-existing alibabacloud-tea-openapi 0.4.4 versus cryptography 50.0.0 conflict; uv pip sync --dry-run was inspected but not applied. The final fresh exact-current scoped review returned clean five-key verdicts for auth/storage/rate-limit, tenant/group/session isolation, and analytics/export/parser/upload boundaries. JSON stores remain runtime-authoritative; no production operation has been performed.

Newly verified local PostgreSQL evidence — 2026-08-15

  • A temporary local PostgreSQL database completed alembic upgrade head, the 7-table tenant/uniqueness runtime probe, ORM-vs-migration parity, offline PostgreSQL DDL checks, the JSON importer dry-run/apply/idempotency/conflict-rollback probe, and alembic downgrade base; zero application tables remained and all temporary databases/fixtures/backups were cleaned up.
  • This closes the local PostgreSQL schema/runtime and fixture-importer probes only. Real JSON snapshot parity/rollback, runtime repository cutover, Redis persistence, Docker image/runtime smoke, real-provider QA, authenticated production smoke, and production approval remain open.

Do not restore public/untrusted access. An async reviewer packet created before the latest auth-lock state reported a password auth_version race; the current tree now serializes change_password() and update_user_fields(password=...) through the same per-user record lock, and the deterministic cross-path regression passes. A surgical independent review of this auth path returned valid passed=true with empty blocking arrays. Legacy-security reviewers deleg_ffba9adf, deleg_03d06f1f, and strict retry deleg_0cc095f8 all timed out after 600 seconds without JSON and are no verdicts. Final one-call retry deleg_e61f99ff returned complete five-key JSON with passed=false because the extraction output exceeded the capture window; it is a limitation/no-approval verdict. The latest full-scope reviewer deleg_93ef64c5 timed out after 600 seconds without a complete five-key JSON verdict; it is no approval. The replacement batch deleg_df17f04e was stopped because it began before the final service-boundary hardening and is not approval. Fresh exact-current review batches deleg_e835e807 and deleg_4ec7eb5a completed for auth/storage/rate-limit, tenant/group/ownership/session isolation, and analytics/export/parser/upload boundaries. All three scoped verdicts returned complete five-key JSON with passed=true, security_concerns=[], and logic_errors=[]. S4.3 reviewer deleg_c2e728d8 returned valid passed=false after finding the offline Alembic dialect bypass; the shared validator and regression test fixed it, and fresh post-remediation reviewer deleg_40e8edf9 returned valid passed=true with empty blocking arrays. Combined with the earlier dependency/schema review deleg_e672880a, the S4.2/S4.3 code-schema review scope is closed. Only complete five-key verdicts with passed=true, security_concerns=[], and logic_errors=[] close review gates. Docker is unavailable locally. Local fixture-based browser/mobile E2E passes, but real-provider QA, production-authenticated browser QA, and production operations are not run. The restricted deployment checklist (bootstrap credential change, JWT secret rotation, audit inspection, fresh authenticated smoke) remains pending and requires explicit operator approval.

cd backend
uv run python scripts/test_m0.py              # auth/roles/no-self-reg
uv run python scripts/test_m1.py              # group create + role visibility
uv run python scripts/test_setup.py           # first-time admin setup (email+password+ToS)
uv run python scripts/test_security.py        # path traversal / IDOR / XSS
uv run python scripts/test_scenario.py        # 2 scenarios (social/f2f_call), recontact default
uv run python scripts/test_e2e.py             # full flow -> won via judge
uv run python scripts/test_ip_protection.py   # secret fields hidden from admin
uv run python scripts/test_saas_tenant.py     # tenant isolation + rate-limit + audit
uv run python scripts/test_user_journey.py    # idea-flow end-to-end
uv run python scripts/test_variant.py         # clone-persona-from-persona
uv run python scripts/test_resume_decision.py # resume + per-turn LLM decision

# run backend (serves frontend/dist only when a local build exists; Docker builds it reproducibly)
cd backend && uv run python run.py        # Flask :5001

Key behaviors (implemented)

  • One-shot: 1 persona = 1 chat per user; result final (won/lost). SessionStore enforces.
  • Resume: unfinished session resumes on re-entry — no scenario re-pick (same session+scenario).
  • Win/loss = per-turn LLM judge (Simulator.evaluate_turn): every customer reply is evaluated → {mood, decision(buy|walk|try|pending), score_delta, reason}; session auto-ends (close + summary) when decision = buy/walk/try — no manual "สรุปผล" button (on try a system note says the customer will trial first and come back). Not fixed keywords.
  • 2 scenarios only: social, f2f_call. Unknown → social.
  • Recontact = persona trait (not a scenario): chats normally, then at turn ≥ 2 a time-lapse system note (" ผ่านไป 2-3 สัปดาห์…"), then re-engages warmer.
  • Persona variant: POST /api/groups/<gid>/personas/<pid>/variant — new persona (new id) that locks pains/objections/levers/tolerance/special/recontact/goal/budget/difficulty/tier/product but varies identity (name/profession/age/location/background/personality/opener). Lets a trainee re-practice the same challenge (one-shot is per-persona). Trainee-created variants go to the trainee's owner-private group; admin-created variants extend the shared admin pool. UI button on finished personas routes trainees to the private group.
  • Auto 15 personas on create; no "เพิ่มเติม" button (TARGET=15, retry up to 3× + accept ≥ 8 so real LLM under-count doesn't 500).
  • IP protection: admin persona responses use an explicit allowlist and omit secret/process fields (pains, objections, negotiation_levers, opener, tolerance, rootCause, resolutionConditions) plus any future unapproved fields; full canonical data is only for super_admin.
  • SaaS Phase 13 done: tenant isolation (g.org_id + assert_tenant), login/chat rate-limit, audit log (data/audit/audit.jsonl), org plan/seats/active model + PATCH /api/admin/orgs/<id>, ToS consent on setup, org-scoped signed expiring CSV export (5-min HMAC).
  • Reduce raw JSON in UI: list_groups returns lightweight summary (persona_count, no full array); persona detail rendered as readable form/cards (pain = line-by-line, not [object Object]).

Credentials / data (testing)

  • Production bootstrap super-admin is admin; its initial password must come from BOOTSTRAP_ADMIN_PASSWORD and is never printed or hard-coded. First login forces email + new password + ToS consent.
  • Do not record or repeat live credentials in this handoff. Existing live test accounts/data require operator review after restricted deployment and secret rotation.
  • LLM key is a placeholder on local .env (replace_me). Real analyze/chat needs a real LLM_API_KEY (+ LLM_PROVIDER/LLM_MODEL/LLM_BASE_URL) in EasyPanel env then redeploy.

Environment / gotchas

  • Python 3.11 only (system default 3.14 incompatible). Use backend/.venv or cd backend && uv run python ….
  • Tooling guard crash: commands whose first token is ./.venv/bin/python trip a lifecycle guard → always use uv run python. Prefix PYTHONPATH= when needed.
  • Frontend build: cd frontend && npm run build (works even with allowScripts restrictions). frontend/dist/ is generated and ignored; Docker builds it in the image, and CI builds to a temporary output directory so clean clones never depend on stale hashed bundles.
  • Deploy pattern: commit + push → webhook auto-deploys in ~3 min. Cannot run Docker locally (no Docker on this Mac) → test with nginx/python http.server or uv run python run.py.
  • No remote push without asking unless it's the established auto-deploy cadence.
  • search_files/read_file sometimes misdetect files as binary (e.g. users.py, some .vue); use terminal + sed -n 'N,Mp' <file> or awk for those.

Wire-authorized / known-limits

  • Chrome/Vivaldi drivable via computer-use; current model (DeepSeek V4 Flash) reads screenshots imperfectly, and window-edge resize to a 320px viewport didn't land — mobile visual QA at 320/500px still needs the user's eyes (open live URL on phone, or DevTools device toolbar). Responsive CSS (640px, single-column, flex-wrap, .btn-back) is present + deployed.

Next actions / backlog (also docs/FUTURE_WORK.md)

  • 2026-08-19 UX + marketing-site push (operator-approved): the app UX/UI redesign (8 files) and new website/ landing site are uncommitted on main. Commit + push ships the redesign to the live EasyPanel app AND adds the marketing site to the repo. Confirm production JWT_SECRET is set (≥32 chars) in EasyPanel env before/with the push (backend fails closed without it). Then re-verify the live bundle. Optionally deploy the website/ landing page as a standalone static site (e.g. nginx / EasyPanel static or another host) — serve from website/index.html; set real pricing before public launch.
  • Sprint 1 live-operation gate: deploy behind restricted access, set/verify BOOTSTRAP_ADMIN_PASSWORD, rotate JWT_SECRET after the patch, inspect audit data, and run a fresh authenticated smoke. Do not open public access before this checklist.
  • Sprint 2: implementation and local regression gate complete; retain current tests as the contract.
  • P1: run real-provider QA and browser/mobile E2E; local contract tests use deterministic fakes.
  • P1: verify final-judge coaching quality with representative provider outputs; correctness and idempotency are locally covered.
  • P1: remove any stale admin chat entry points in live UI after restricted authenticated smoke.
  • P1/P2 ops: add PostgreSQL repository adapters, browser E2E, and real-provider QA before production rollout. Upload limits, cleanup, fail-closed JWT/bootstrap configuration, Gunicorn, Docker healthcheck, and .dockerignore are implemented and tested locally.
  • S4.2: schema reviewer findings (nullable audit actor link, CWD-relative Alembic paths, ORM/migration defaults drift, unsupported partial-index dialect, and offline batch rendering) plus dependency reproducibility were remediated locally; combined exact-current evidence from deleg_e672880a and post-remediation deleg_40e8edf9 closes the code/schema/dependency review scope. Temporary-local PostgreSQL execution, ORM/migration parity, offline DDL, and schema rollback now pass; Docker build, importer data parity/rollback, repository cutover, and production-safe rate-limit/audit storage remain blocked.
  • S4.3: org/users, groups/personas, and sessions/messages repository contracts and SQLAlchemy adapters are local-only; cross-tenant user lookup and offline-dialect remediations are locally verified and independently approved by deleg_40e8edf9. PostgreSQL schema parity passes on temporary local databases, while runtime repository cutover remains blocked.
  • S4.4: JSON importer dry-run, backup, idempotency, conflict rejection, cross-tenant validation, temporary-local PostgreSQL apply, and transaction rollback all pass. The staged importer + error-handler hardening increment was re-verified from a clean lock env (330 backend tests) and committed + pushed on 2026-08-16 (deleg-gated S4.4 importer already independently reviewed in the prior packet). Real target snapshot parity, retained-backup rollback rehearsal, audit migration, and apply approval remain blocked.
  • 2026-08-15 legacy-security remediation: the latest valid reviewer finding about legacy ratelimit.json migration was remediated with fail-closed marker/digest validation, structured keys, duplicate-preserving import, and idempotent import metadata. Current local evidence is 319 backend tests, 166 focused regressions, and 4 frontend unit tests. Three fresh exact-current scoped reviewers returned clean five-key verdicts; see docs/engineering-log/2026-08-15-final-security-gate.md.
  • Real /legal page (setup links to it), billing/payments, per-tenant storage volume, compressed persona recipe, export-token polish.
  • Mobile visual polish per user feedback on real device.
  • Deeper: letting admins bulk-import personas, analytics drill-down per persona variant.
  • docs/PLAN.md, docs/SAAS_PLAN.md, docs/FUTURE_WORK.md.
  • docs/engineering-log/2026-08-13-idea-implementation-audit.md — latest evidence-based audit, runtime probes, and prioritized fixes.
  • docs/engineering-log.md — status index.
  • docs/engineering-log/2026-08-15-final-security-gate.md — final local verification and clean exact-current scoped review gate; production-operation limits remain.
  • docs/engineering-log/2026-08-15-postgresql-runtime-gate.md — temporary-local PostgreSQL schema/runtime, parity, offline DDL, and migration rollback evidence.
  • docs/engineering-log/2026-08-15-postgresql-import-gate.md — temporary-local PostgreSQL importer dry-run/apply/idempotency/conflict-rollback evidence.
  • docs/test-evidence/2026-08-15-postgresql-runtime.md — PostgreSQL automated gate evidence and remaining operational boundaries.
  • docs/test-evidence/2026-08-15-postgresql-import.md — importer evidence separated from real target apply and audit migration.
  • docs/engineering-log/2026-08-09-idea-flow-qa-deploy.md (this session: idea-flow UX, 2-scenario + recontact trait, live QA + auto-deploy, per-turn LLM judge, persona variant, 15-persona auto-gen).
  • docs/engineering-log/2026-08-14-final-review.md — prior five-finding remediation and review history; current exact-tree approval remains pending.
  • docs/engineering-log/2026-08-15-auth-version-review-reconciliation.md — current auth-version lock-path verification and pending fresh reviewer gate.
  • docs/engineering-log/2026-08-15-s4-2-schema-foundation.md — test-first SQLAlchemy/Alembic schema foundation, tenant constraints, and generated-dist cleanup.
  • docs/engineering-log/2026-08-15-s4-3-org-users-repositories.md — tenant-scoped repository contracts and adapters, with no runtime cutover.
  • docs/engineering-log/2026-08-15-s4-3-offline-dialect-remediation.md — offline Alembic dialect finding, test-first fix, verification, and pending review.
  • docs/engineering-log/2026-08-16-s4-4-importer-errorhandler-commit.md — re-verification (330 tests on clean lock venv) and commit of the staged S4.4 importer + error-handler hardening increment.