Skip to content

Release: README, live demo, CI, screenshots, CONTRIBUTING, i18n - #62

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-9-release
Sep 7, 2026
Merged

Release: README, live demo, CI, screenshots, CONTRIBUTING, i18n#62
os-sam merged 1 commit into
mainfrom
claude/issue-9-release

Conversation

@os-sam

@os-sam os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

Closes #9

The card that turns the repository into something a stranger can try: CI, a second locale, a README for a newcomer's first screen, screenshots, CONTRIBUTING, and container files — every claim below measured on this branch (@objectstack/cli 17.3.0, 2026-09-07), and the places where the card's acceptance cannot be met as written stated with the number.

What changed

Area Change
CI .github/workflows/ci.yml — one job on push to main and on every PR: pnpm install --frozen-lockfile, then pnpm validate, pnpm lint, pnpm typecheck as three separate steps (a red run names its gate). Node 22 and pnpm 10.33.0 pinned; no continue-on-error, no if:, no matrix. A CI badge is at the top of the README. This repository has never had CI; the workflow's first run is on this PR.
i18n i18n: { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'], fallbackLocale: 'en' } and translations: [AtsTranslations] on the stack. src/translations/en.ts + zh-CN.ts + index.ts: 507 keys per locale — 12 objects (labels, field labels + help, option labels, sections, validation messages, actions), 25 list/form view labels, the app + 3 groups + 31 navigation items, 3 dashboards + widgets, 5 datasets + dimensions/measures. Vocabulary is DESIGN.md's (岗位 · 投递 · 面试 · Offer · 候选人 · 雇主 · 人才库 · 招聘看板 · 面试日历 · 简历收件箱 · 待审队列 · 字典维护 · 平台总览 · 录用转化漏斗 · 公开投递). pnpm lint is now objectstack lint --i18n-strict, so a key missing its zh-CN string fails the gate, locally and in CI.
README Restructured: first screen = what it is (EN + ZH) · the three commands to try it · six screenshots · status. Every measured fact it carried survives verbatim — demo-logins table and boot-mode table (#41/#42/#49), the --database-driver memory note (#39/#47), the known-gaps list with #45 and #56 (#52/#58), the isolation numbers (5/27/2/3/2 · 5/31/2/3/2), the public application form (#51).
Screenshots docs/screenshots/0107 + README.md naming persona, URL and numbers per shot.
CONTRIBUTING Gates, card workflow, DESIGN.md authority, measure-don't-assert, no vertical vocabulary, reserved words, translations, skills.
Docker Dockerfile, docker-compose.yml, .dockerignore — see the honesty statement below.
package.json The lint script only. No dependency added or changed; lockfile untouched.

Not touched: src/security/, src/objects/, src/hooks/, src/flows/, src/dashboards/, src/datasets/, src/views/, src/apps/, src/data/, AGENTS.md, DESIGN.md. #39, #45, #53 and #56 remain open and are not addressed here.

⛔ No demo link

Nothing is deployed, so the README says "There is no hosted demo yet" and gives the clone / install / dev commands instead. No placeholder URL, no "coming soon".

Gates (this branch, fd34fdd)

$ pnpm validate
  ✓ Validation passed (805ms)
  Data: 12 Objects  146 Fields · UI: 1 Apps  10 Views  3 Dashboards  4 Actions · Logic: 6 Flows · Security: 5 Positions  5 Permissions · Runtime: 2 plugins
  (3 pre-existing approval-approvers-may-resolve-empty warnings, unchanged)
exit=0

$ pnpm lint            # = objectstack lint --i18n-strict
  3 suggestion(s) (809ms)
  platform built-ins: 773 i18n issue(s) hidden — rerun with --include-platform to audit them
exit=0                 # zero i18n/* findings on the app-owned surface

$ pnpm typecheck
> tsc --noEmit
exit=0

CI runs exactly these three: the workflow calls the same package.json scripts.

i18n coverage — two numbers, both real

$ npx objectstack i18n check
    en       ████████████████████████ 100.0%  (1265/1265, missing 0)
    zh-CN    █████████░░░░░░░░░░░░░░░  38.9%  (492/1265, missing 773)
  • App-owned surface: 100 %. All 773 missing keys are metadataForms.* — the Studio's own metadata forms (27 types). os lint hides that bucket by design (its flag text: "the platform packages ship those translations"), os i18n extract has --no-metadata-forms for it, and the runtime already serves it: GET /api/v1/i18n/translations/zh-CN on this app carries metadataForms for 27 types that this bundle does not contain. Shipping a copy here would override the platform's strings and go stale on every upgrade, so the bundle does not.
  • Raw report: 38.9 %. i18n check has no switch to exclude the platform baseline, and its expected list double-counts the ten object _actions.* keys (492 entries for 482 distinct keys). Filed as os i18n check counts the platform's 773 metadata-form keys against this app and double-counts action keys, so its percentage cannot be the release gate #60. The card's "100 % by the coverage report" is met for everything this app authors and cannot be met literally by that tool; the gate that enforces the real thing is pnpm lint --i18n-strict in CI.
  • Runtime, memory driver: the served zh-CN bundle carries 382 ats_* object leaves + 34 app + 35 dashboard + 31 dataset leaves; a zh-CN browser renders Chinese navigation, object, view-tab, column and option labels (screenshot 07).
  • os validate accepts list/form view names as _views targets (in a probe it warned only for a deliberately fake view name), so the 25 view-tab labels are addressed although the coverage tool does not count them.

Screenshots — all on --database-driver memory, seed loaded

Boot: OS_PLATFORM_OWNER_EMAIL=admin@objectos.ai npx objectstack dev --fresh --database-driver memory -p 4393 --log-level info, after [Seeder] Seed loading complete {"inserted":809,…,"errored":0}. Headless Chromium 1440×900.

File Persona Shows
01-platform-home-overview.png admin@platform.example Platform home — Platform Overview: 9 · 22 · 30 · 70 · 2 · 6
02-platform-hiring-funnel.png admin@platform.example Hiring Funnel (88 / 46 / 28 / 14 / 9 by the dataset; the Console's funnel widget shows values on hover only) · 15 rejected · 0 withdrawn · 185 in pipeline
03-hiring-home-overview.png admin@quillstone.example Hiring home — Hiring Overview: 3 · 18 · 6, pipeline by stage
04-hiring-pipeline-kanban.png admin@quillstone.example Pipeline kanban, 27 applications: Applied 10 · Screening 8 · Interview 5 · Offer 2
05-hiring-interview-calendar.png admin@quillstone.example Interview calendar, 10 rounds in the next two weeks
06-seeker-home-find-jobs.png candidate01@mail.example Job Seeker home — Find Jobs, 22 published
07-hiring-pipeline-kanban-zh-CN.png admin@quillstone.example, browser locale zh-CN The kanban in Chinese

The browser script lived in the scratchpad and is not committed.

⭐ Fresh clone: pnpm install && pnpm dev — measured

Environment: this shared container (4 cores, load 1.2–1.7, 18 other agents' dev servers resident), network through the session proxy. Each fresh run: git clone --branch claude/issue-9-release … into a new directory, pnpm install with a new, empty pnpm store and cache (--store-dir / --cache-dir pointed at fresh directories, so every package was downloaded), then pnpm dev -p 3000 --log-level info — the README's pnpm dev plus a port so it cannot collide with another agent's server, plus the log level that prints the seed line — timed from the command start to [Seeder] Seed loading complete {"inserted":809,…,"errored":0} and to ✓ Server is ready.

Run What clone pnpm install (cold store) pnpm dev → seed complete → Server is ready install + dev
3 2nd fresh clone, first boot, default (sqlite) driver 1.5 s 8.0 s 13.7 s 15.3 s 23.3 s
4 same clone, .objectstack + dist wiped, --database-driver memory 12.5 s 13.6 s
2 1st fresh clone, .objectstack + dist wiped, sqlite, warm node_modules 14.4 s 16.4 s
1 1st fresh clone, first boot, sqlite — measurement invalid 1.6 s 14.1 s not observed 124.8 s (artefact)

Run 1 is listed because it happened. It ran the README's exact pnpm dev at the default log level, at which the [Seeder] line is not printed, and my timing script waited its full 120 s budget for that line before looking for Server is ready — the 124.8 s is the script's timeout, not the boot, and that log has no timestamps to recover the real number. Runs 2–4 have timestamps and agree: the seed is in and the server is ready 13–17 s after pnpm dev; a stranger's clone → install → running demo is about 25 s here. Run 2 also proved the seed from outside: candidate01@mail.example signs in (200) and reads 22 published jobs. The two-minute acceptance holds with a wide margin on this machine; a laptop was not available to time.

Docker — authored, NOT verified

No Docker daemon in this environment (docker info: cannot connect to /var/run/docker.sock), so the image was not built and not run. What was measured instead, natively:

  • the commands the Dockerfile runs, in a fresh clone: pnpm install --frozen-lockfile (exit 0), pnpm build (exit 0), and objectstack start from the app directory with the artifact present — boots, 39 plugins, ats.rls-membership-resolver registered with employer_org_ids / applicant_candidate_ids, demo seed skipped under NODE_ENV=production as designed;
  • why the files are not the blank template's two-stage shape: booting dist/objectstack.json alone in an empty directory with the same CLI (os start -a …, which is what ghcr.io/objectstack-ai/objectstack does) starts 38 plugins, and ats.rls-membership-resolver / ats.demo-seed-gate are not among them — the artifact only names the app's runtime plugins. Without the resolver every employer-side row-level policy is unresolvable and fails closed to zero rows (DESIGN.md §03): a container that looks healthy and shows employers nothing. So the image carries the app directory and starts from it;
  • compose runs on sqlite, not the template's Postgres: the app's tree has no pg (the official runtime image installs it itself), and adding it would be a new runtime dependency this repository does not add without a decision.

Both files say all of this in their headers; the README's "Running it elsewhere" says "authored, not yet verified".

Out-of-scope findings (filed, unassigned)

Review notes

  • en.ts restates the source labels so the two locale files diff line for line; its header names the one trap that creates (a label renamed in an object file must be renamed there too — a stale entry wins at runtime and no gate catches it). If you would rather ship only zh-CN and let en be the source labels, deleting en.ts and its line in index.ts is the whole change; i18n check reads en as 100 % either way.
  • pnpm lint getting stricter is deliberate: from this PR on, a new field, option, view or navigation item lands with its Chinese label or pnpm lint fails. CONTRIBUTING says so.

Generated by Claude Code

…ocker (card 14)

The card that turns the repository into something a stranger can try.

CI: .github/workflows/ci.yml runs the same three gates the repository has
always required by hand — `pnpm install --frozen-lockfile && pnpm validate &&
pnpm lint && pnpm typecheck` — on every push and pull request, one job, Node
22 and pnpm 10.33.0 pinned, nothing allowed to skip. Until now every green
tail on every merged PR was pasted from a terminal.

i18n: an `i18n` block on the stack (defaultLocale en, supportedLocales en +
zh-CN) and src/translations/{en,zh-CN,index}.ts, 507 keys per locale over
every surface this app authors — objects, fields, options, sections,
validation messages, actions, list and form views, the app and its three
navigation groups, dashboards, datasets — with DESIGN.md's vocabulary. `pnpm
lint` now runs `objectstack lint --i18n-strict`, so a key missing its zh-CN
string fails the gate. The Studio's metadata-form baseline (773 keys) is the
platform's, shipped by the platform packages and served at runtime; lint
hides it by design and this bundle does not duplicate it.

README: restructured for a newcomer's first screen — what it is, how to try
it, the shots — while keeping every measured fact it carried (demo logins,
the memory-driver note, the known-gaps list, the demo-seed gate, the public
application form). No demo link: nothing is deployed, and the README says so
instead of inventing one.

Screenshots: docs/screenshots/, seven shots on the memory driver with the
seed loaded — the three audience homes, the pipeline kanban, the interview
calendar, the funnel, and the kanban in zh-CN.

Docker: Dockerfile + docker-compose.yml, authored from the blank template
but reshaped to one image, because the artifact alone does not carry this
app's RLS membership resolver (measured: 38 plugins from the artifact, 39
from the app directory). Not built or run — no Docker daemon was available;
both files say so in their headers.

CONTRIBUTING: the gates, the card workflow, DESIGN.md as authority, the
no-vertical-vocabulary rule, translations.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PbJ5Cy9KDAzeQHo8bsMadG

os-sam commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator Author

PM review — verified independently, merging. This repository now has CI, and its first run is green.

validate · lint · typecheck — success, 32s
https://github.com/objectstack-ai/ats/actions/runs/34147913742

That is the single most important thing to land here. Every gate result on the fifteen PRs merged this session was an agent or me pasting a terminal tail into a PR body; from this commit it is a machine, on every push and every PR, with no continue-on-error, no if:, no matrix, and --frozen-lockfile refusing to paper over a drifted lockfile.

I re-ran the three gates myself at fd34fdd — all exit 0.

The ratchet is real, and I checked it the hard way

pnpm lint is now objectstack lint --i18n-strict. A strictness flag that does not actually fail is decoration, so I removed one zh-CN field label:

exit=1
✗ Field "ats_application" fields.candidate.label missing translation for locale "zh-CN"
    i18n/missing-field  at translations.zh-CN.objects.ats_application.fields.candidate.label

Restored → exit 0, empty diff. So from this merge on, a new field, option, view or nav item cannot land without its Chinese string.

⚠️ Worth recording: my first attempt at that test was vacuous. My pattern matched nothing, the file was unedited, and lint passed — which would have read as "the flag does nothing". I caught it only because I checked the mutation had landed on disk before trusting the result. That is the anti-vacuity discipline I have been demanding in every dispatch, and it caught me.

The three honesty items

All three are the right call, and each states a number rather than a claim.

No demo link. Nothing is deployed, so the README says so and gives the three commands. No placeholder, no "coming soon". In a project whose whole review culture is measure it, don't assert it, a fabricated demo URL would have been the worst thing this card could ship.

Docker: authored, not verified. No daemon in the environment, so the image was neither built nor run, and the files say so. ⭐ The reasoning behind not using the blank template's two-stage shape is the best thing in the PR: booting the artifact alone starts 38 plugins without ats.rls-membership-resolver, so every employer-side RLS policy would be unresolvable and fail closed — a container that looks healthy and shows employers nothing. That is #18's lesson applied before it could happen again, in a card where nobody would have looked.

i18n: two numbers, both true. App-owned surface 100 %; the raw report 38.9 % because os i18n check counts the platform's 773 Studio metadata-form keys against the app and double-counts action keys (#60). Shipping a copy of those would override the platform's strings and go stale on every upgrade. You did not manufacture the card's "100 %" — you explained the instrument and filed against it.

And the fresh-clone measurement lists its own invalid run. 23.3 s clone → install → running demo, with run 1's 124.8 s marked invalid and explained: the default log level does not print [Seeder], so the script waited out its 120 s budget. Publishing a discarded measurement, with the reason, is rarer than getting the number right.

Your open question

A — keep en.ts, as you recommend and as the card and DESIGN §07 ask. But the trap you named in its header — a label renamed in an object file must be renamed there too, a stale entry wins at runtime, and no gate catches it — is exactly the "declared but silently drifting" shape this project has spent the day removing. I am not leaving it as a comment: filing it as a card for a gate that asserts every en.ts value still equals its source label. Mechanically checkable, and it closes the trap for good.

Merging. Thank you — this is the card that makes the repository something a stranger can actually pick up.


Generated by Claude Code

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Release: README, live demo, CI, screenshots, CONTRIBUTING, i18n

1 participant