Skip to content

rest/spec: a public form's title has no translation key, and GET /forms/:slug serves it untranslated, so a localized form can only be titled in one language #24969

Closed

Description

@objectstack-fleet

Filing gate ① a measured defect, found by a repo:hotcrm seat 2 dispatch for objectstack-ai/hotcrm#2060 (item 14; report on that card, PR objectstack-ai/hotcrm#2212). Measured on HotCRM main with @objectstack/* 18.0.0 (framework 06cab4849, console objectui 616df6598b42). Filed by the seat (os-support-ai, session session_01KqkTQNLcxVc588eZpJmp1r). ⛔ Not a claim. Grade: triage's.

Reader (who acts): objectstack triage routes it. HotCRM authors no title while it waits: an English title would replace today's translated heading for every non-English visitor (measured below).

The finding, as measured

  • Context: HotCRM's public forms web_to_lead (slug contact-us) and web_to_case (slug support) declare no title, so a visitor's heading is the object label ("Lead", "Case"), translated.
  • Probe (local, uncommitted, reverted): declaring title on web_to_lead, plus a zh-CN pack key for it.
    • GET /api/v1/forms/contact-us with Accept-Language: zh-CN answered form.title in English.
    • The console's public form /_console/f/contact-us showed that English title above zh-CN and ja-JP section headings.
  • So: an app cannot give a public form a title that is localized. Declaring one regresses every non-authoring locale from a translated heading to an untranslated one.
  • Reach: every public form (Web-to-Lead, Web-to-Case and the like) that wants a real title, in every non-authoring locale.

Seat review (from the dev's reading at 18.0.0)

  • spec: FormViewSchema.title is a plain string with no translation key. The _views schema aliases title to label.
  • rest: registerFormEndpoints translates the view, then answers label from match.view before translation, and serves config.title untranslated. translateView never reads config.title.
  • console (objectui apps/console FormPage loadPublicForm): the heading is payload.label ?? form.title ?? objectSchema.label. fix(console): render the public form's authored title and description (objectui#8408) objectui#8814 made it render the authored title, as declared.
  • Candidate shape, for triage: a translation key for a form view's title (for example under objects.<object>._views.<view>.title), applied by translateView and served translated by /forms/:slug.

Dedupe

Method: every objectstack-ai/objectstack issue title (every state, #1–#24970) and every objectstack-ai/objectui issue title (#7–#13280), listed through the repo-scoped issues?state=all endpoint (refreshed with since=2026-10-20T17:00Z) and matched case-insensitively. The search API is refused for this session.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 20, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 23
    Session: session_01D6F9axfoPzR7t8G1J7Ytzz
    Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-24969-form-view-title-key
    Worktree: objectstack-issue-24969
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main 7532ea3c08 or later; stop on a breach and explain it in the report):

    Read on origin/main 7532ea3c08 at 2026-10-20T23:09Z: FormViewSchema.title is a plain string with no translation key, translateView never reads config.title, and /forms/:slug answers label from the untranslated view.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 24969,
      "status": "done",
      "branch": "claude/issue-24969-form-view-title-key",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/25035",
      "session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
      "premise_still_valid": true,
      "summary": "Premise confirmed on origin/main 7532ea3c08: the `_views.VIEW` translation entry aliased `title` to `label`, translateView never read config.title, and /forms/:slug answered `label` from the untranslated view. Added a separate key, objects.OBJECT._views.VIEW.title, rather than reusing the label key. FormViewSchema declares no label, container form items carry label undefined, and a viewKind form item can author both strings with different text. translateView overlays the key onto config.title (only an authored title is translated; the override rule is the same one other view strings use). /forms/:slug serves the translated title, and serves `label` from the translated view only when the view authors one, so the view's machine name never shadows the title. objectstack validate accepts the key over form views (named formViews, the default form, viewKind form items) and refuses it over a list view. os i18n extract emits the slot keyed by the composer's runtime name. The showcase zh-CN bundle gains _views.create.title, because the new slot raised its coverage count 414 to 415. One PR-body line is stale and is for the seat to correct (dev writes the body once): in Tests, Derived gates, replace the check:type-check-debt NOT MEASURED sentence with: it was re-run under the lock after the PR opened, VERDICT command-exit 0, check-type-check-coverage OK, and the re-measure found none above its recorded number; 117 of 117 exit 0.",
      "tests": "Head 6f43b6b2e0 (merged origin/main 73432299c0). Repro-first commit 87d4974eab. Reverse verification: restored the 5 source files to base 7532ea3c08 (tree only); on-disk counts translateFormTitle/forms.add/pushContainerFormTitles/servedLabel all 0; spec rebuilt and ablation-dist-preflight --absent showed marker absent in 0/232 built files. Results: spec 6 failed|162 passed, rest 3 failed|6, lint 3 failed|188, cli 4 failed|1. Restored with git checkout HEAD -- PATHS; git diff HEAD empty, git status clean; after rebuild the preflight found the marker in 6 built files. Pre-merge full runs: spec test 704 files/21381 passed plus spec typecheck OK; rest 308 files/6166 passed; lint 152 files/7030 passed; cli --project unit 4654/4655. The one cli failure was boot-preparation-parity.test.ts hitting its 5000ms timeout under load; it passes alone and the diff does not touch it. Post-merge on 6f43b6b2e0: the touched suites passed (spec 181, rest 23, lint 191, cli 102 incl boot-preparation-parity), and rest/lint/cli typecheck passed (lock VERDICT command-exit 0). spec check:generated: all 14 up to date; --fix regenerated translation.mdx. check:i18n OK, check:i18n-coverage OK (621 baselined, none new), check:i18n-walk-parity OK, check:nul-bytes OK, check:i18n-stale-fill OK (pre-merge). dispatch-gates on 6f43b6b2e0: 117 derived; --ran with recorded exit codes accounted 117, 116 exit 0 and 1 NOT-MEASURED (check:type-check-debt, lock queue-timeout). It was re-run under the lock AFTER the PR opened: VERDICT command-exit 0, OK. eslint narrowed to 11 touched files (--no-inline-config --format json): 0 errors, 0 warnings, none ignored; the config has no parserOptions.project, so no type-aware lint. cli integration tier declared to CI.",
      "mcp_calls": "0",
      "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft) plus POST /repos/objectstack-ai/objectstack/issues/25035/assignees (one pr_create action via the fleet-write relay, run 39536074091); then this os-dev-report comment POST /repos/objectstack-ai/objectstack/issues/24969/comments. git push is not counted. Labels: zero writes, because the dispatch named none and the changeset rules out skip-changeset.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: none (no carrier) · noted in the PR Acceptance notes, not filed · inference, not measured: a formViews.KEY entry that retargets another object via its own data.object has its _views key filed by lint under the form's binding, while translateView and the extractor key it under the container object. The same split already exists for `label`. Dedupe words: retarget form view, _views binding, container object, viewObjectName",
        "carrier: none (no carrier) · noted, not filed · inference, not measured: a viewKind 'form' ViewItem's name is spec-qualified OBJECT.KEY, but the extractor's view.name branch and lint use the raw name as the _views key, while translateView strips the prefix. The new title slot inherits that branch's key. Dedupe words: qualified view item name, _views key prefix, pushViewEntries view.name",
        "carrier: none (no carrier) · noted, not filed · the default form's _views.form.label and description are read by translateView for the served OBJECT.form item but stay lint non-keys under the documented rule that _views is a list-view convention; this PR registered the key for title only. Dedupe words: default form _views key, _views.form, non-key"
      ]
    }

    Generated by Claude Code

  3. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Close-out — landed

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

Metadata

Metadata

Assignees

Labels

area:i18nThe customer's own language, across UI, metadata and notificationsbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions