Skip to content

hotclm's own notifications reach a zh-CN user in English — "Contract approved: …", "Renewal decision due: …", "Expired: …" — though the platform's notify template path is per-recipient localized #160

Closed

Description

@objectstack-fleet

Filing class: ① user-visible defect — reach: public door, measured in a browser on main (18.1.0 at the time, pnpm demo:zh), code read on main 21f2319. Reader: the hotclm lane.

Symptom

Business Requester 1 (zh-CN UI) opens the bell after their NDA is approved and reads: Contract approved: 回归测试 NDA — 已有相对方 / Your contract … has cleared every approval rung. It can now go out for signature. The email channel carries the same English. Every notification hotclm sends itself is an English literal in a notify node's inline title / message:

  • src/flows/contract-approval.flow.ts:391-392 (approved), :418-427 (rejected)
  • src/flows/signature-record.flow.ts:336-337, :354-358 (executed copy missing, formalities outstanding)
  • src/flows/renewal-notice.flow.ts:195-196 (renewal decision due)
  • src/flows/expiration-sweep.flow.ts:86-87, :158-159 (expired, renewal drafted)
  • src/flows/legal-review-sla.flow.ts:80-81, :103-104 (30 / 60 days in review)
  • src/flows/turn-stalled.flow.ts:62 (waiting on the counterparty)

These are the messages a requester, a lawyer and the head of legal act on, and in a Chinese workspace they are the only English sentences left on screen.

Fix

The platform's inline title / message are "not localizable" by contract (NotifyConfigSchema). The localizable path is template + templateData: an email_template bundle with en and zh-CN members, resolved per recipient (sys_user.locale, else the deployment default) at delivery, for the inbox channel as well as email (service-messaging/src/inbox-channel.ts, #9225). Move each node above to a template; keep the {{ }} values as templateData.

If a template turns out not to render on the inbox channel in 18.2, stop and file that on the platform rather than working around it here.

Dedupe: hotclm issues (all states) grepped for notification, notify, English, template — #142 is the activity feed (platform-written, frozen at write time) and #157 is child record names; none covers the flows' own notifications.


Filed from the hotclm UI/UX review (2026-10-27).

Activity

  1. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage — 2026-10-27

    User-visible defect, read against main: accepted.

    Route: app-side. Move each notify node's inline title and message onto the platform's per-recipient template + templateData path, with en and zh-CN members, as the card describes. It shares src/flows/** with #156 (in flight), so it is dispatched after #156 lands.

    State: → pm:queue + bug.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 17
    Ruling-ref: none
    Session: session_017nzWFRUo2U7evMkPdVniAn
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-160-notify-templates
    Worktree: hotclm-issue-160
    Domain: repo:hotclm (single-lane sister repo — no domain:*)
    Seat: repo:hotclm#1
    File surface: the notify nodes of src/flows/{contract-approval,signature-record,renewal-notice,expiration-sweep,legal-review-sla,turn-stalled}.flow.ts · the email-template metadata they name (new files under src/ as the platform requires) · translations; stop on breach; explain in the report
    Container & model: S, mode:subagent, model: opus (TIER_DEFAULT; hand-derived for a sister repo), effort: medium
    Clause-②: no
    Metadata-protocol: none
    Responsibility: this app's own notifications (what a zh-CN recipient reads) | none expected | every requester, lawyer and head of legal who receives a CLM notification
    Thread-read: 6439162948
    Premise: re-measured by the dev on origin/main 0159171 (@objectstack 18.2.0) with the declared demo staff (PR #171)
    Serial constraints cleared: #156 landed (PR #170, flows) and #28 landed (PR #171, config / README); round 17 dispatches #43, #160 and #41 together on disjoint files; in flight #169 (lifecycle actions). #19 (seeds) follows #41; #99, #161, #162 (launch wizard) follow.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 160,
      "status": "done",
      "branch": "claude/issue-160-notify-templates",
      "pr": "https://github.com/objectstack-ai/hotclm/pull/174",
      "session": "session_017nzWFRUo2U7evMkPdVniAn",
      "premise_still_valid": true,
      "summary": "Re-measured on origin/main 0159171 (18.2.0, pnpm demo:zh, port 3152): a zh-CN requester's bell and sys_email.subject read 'Contract approved: ...' in English. The ten listed notify nodes now name an email_template bundle (template + templateData). The bundles live in src/email-templates/, one file per flow, and are registered through defineStack({ emailTemplates }). Each has an en-US member (the resolver's only fallback rung; an 'en' member would have no floor) carrying the unchanged English sentence and a zh-CN member. The platform path localizes per recipient as the card claims, on both inbox and email: measured live, including the Console's own language switch, which writes sys_user.locale.",
      "tests": "Browser (Chromium /opt/pw-browsers/chromium, zh-CN context, Console login as each persona), head c4423f7. requester1 (locale zh-CN): bell '合同已批准:主服务协议 — 索伦特制造' (approval end to end through the manager, finance and executive rungs) and '合同已驳回:独立承包人协议 — 卡米尔·博尚'. counsel1 (zh-CN): '执行形式未完成:… 回签副本已收到' and '缺少执行副本:…' (signing rounds completed). requester2 (en): 'Contract rejected: …'. requester3 (unset locale, deployment default en): 'Contract rejected: …'. counsel2 (en): 'Execution formalities outstanding: … Countersigned copy returned.' Email channel: sys_email.subject matches the bell language for all 7 deliveries, status sent. 0 failed sys_notification_delivery rows. Baseline at 0159171: requester1 (zh-CN) bell 'Contract approved: 双向保密协议 — 铜田商旅' in English. NOT MEASURED in the browser: the daily sweeps (renewal decision due, expired, renewal drafted, 30/60 days in review, waiting on the counterparty). POST /api/v1/automation/{expiration_sweep,turn_stalled}/trigger answers 403 PERMISSION_DENIED ('A flow that runs on its own trigger starts there'), so no alternate route was taken. Covered instead by a render proof through plugin-email 18.2.0's EmailService.renderTemplate over createRegistryEmailTemplateLoader(allEmailTemplates), for locales zh-CN, en and fr-FR: bundles=10 members=20 failures=0. No permanent test file was added (the repo has no test suite).",
      "gates": "head c4423f7, each exit captured before any pipe: pnpm install 0 · pnpm validate 0 (7 diagnostics, byte-identical to a git-archive snapshot of 0159171) · pnpm lint 0 (0 warnings, 6 suggestions, identical to 0159171) · pnpm typecheck 0 · pnpm lint:i18n-gate 0 ('0 missing keys across 2 locale(s)') · CLM_DEMO_SEED=1 pnpm validate 0. CI check-runs on PR #174: not read (in_progress at report time).",
      "dogfood_battery": "n/a — hotclm, no ! in the title, Clause-②: no",
      "mcp_calls": "0",
      "api_writes": "2 relay writes: pr_create (objectstack-ai/hotclm#174, read back identical, assignee zhuangjianguo) and this os-dev-report comment on #160. Plus git push of the branch (not REST). Local demo DB writes only on throwaway .objectstack/data.",
      "open_questions": [],
      "out_of_scope_findings": [
        "class: a · reach: inline English in src/flows/obligation-due.flow.ts (due in 7 days / due today / overdue) and src/flows/payment-overdue.flow.ts (instalment overdue): the same defect as #160, but not listed on the card and not in the claim's file surface. Read from code, not measured in a browser (both are daily sweeps, so the 403 on the trigger door applies). After PR #174 the fix is three lines each: a noticeBundle plus template/templateData on the rowBody notice. carrier: the seat (a sub-issue of #160, or a rider on its next card) · dedupe words: obligation notification English, payment overdue notify, zh-CN reminder",
        "class: observation · Console language switch (account menu → 语言) writes PATCH /api/v1/data/sys_user/ID {locale:'zh-CN'|'en'}; the 'en' it writes resolves to the en-US floor member. A hotclm email_template member written as locale 'en' (matching the stack's defaultLocale) would be unreachable from en-US and dead-letter unknown tags; recorded in src/email-templates/_bundle.ts · carrier: none · noted, not filed",
        "class: observation · review_started_at / turn_since render as raw ISO datetimes ('2026-09-12T00:00:00.000Z') in both languages, as the inline English already did · carrier: none · noted in PR Acceptance notes",
        "class: observation · common.md says lint on main has exactly one warning (#64's security-controlled-by-parent-owner-policy-without-opt-in); measured on 0159171 it has 0 warnings and 6 suggestions · carrier: seat · noted, not filed"
      ],
      "file_surface_note": "Beyond the notify nodes and the new src/email-templates/, this PR touches objectstack.config.ts (one emailTemplates: registration, which the platform requires), src/flows/_daily-sweep.ts (RowNotice/rowBody, the builder that emits four of the ten nodes), and signatureExecution in signature-record.flow.ts (adds missingTextZhCN from the zh-CN bundle's formality labels). RenewalStartFlow is untouched (per the seat note on #99).",
      "governed_text_proposal": "none"
    }

    Generated by Claude Code

  4. objectstack-fleet commented on Oct 27, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling-ref: none (triaged defect, comment 6439162948)

    Landed — PR #174 · 2026-10-27

    reading value
    PR #174 MERGED (squash on main), merged at its ACCEPT head c4423f7; Fixes #160 closes this card
    authority maintainer, project thread, verbatim: 「你可以自己审核的就自己合并」

    Release: hotclm's approval, signing, renewal, expiry, review-SLA and turn notifications now arrive in each recipient's language, in the bell and by email. The two reminder flows the card did not list, obligations and payments, go to a follow-up card. State: pm:dispatched removed, assignee cleared.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions