Release: README, live demo, CI, screenshots, CONTRIBUTING, i18n - #62
Conversation
…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
|
PM review — verified independently, merging. This repository now has CI, and its first run is green. 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 I re-ran the three gates myself at The ratchet is real, and I checked it the hard way
Restored → exit 0, empty diff. So from this merge on, a new field, option, view or nav item cannot land without its Chinese string. The three honesty itemsAll 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 i18n: two numbers, both true. App-owned surface 100 %; the raw report 38.9 % because 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 Your open questionA — keep Merging. Thank you — this is the card that makes the repository something a stranger can actually pick up. Generated by Claude Code |
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/cli17.3.0, 2026-09-07), and the places where the card's acceptance cannot be met as written stated with the number.What changed
.github/workflows/ci.yml— one job on push tomainand on every PR:pnpm install --frozen-lockfile, thenpnpm validate,pnpm lint,pnpm typecheckas three separate steps (a red run names its gate). Node 22 and pnpm 10.33.0 pinned; nocontinue-on-error, noif:, no matrix. ACIbadge is at the top of the README. This repository has never had CI; the workflow's first run is on this PR.i18n: { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'], fallbackLocale: 'en' }andtranslations: [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 lintis nowobjectstack lint --i18n-strict, so a key missing its zh-CN string fails the gate, locally and in CI.--database-driver memorynote (#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).docs/screenshots/01–07+README.mdnaming persona, URL and numbers per shot.DESIGN.mdauthority, measure-don't-assert, no vertical vocabulary, reserved words, translations, skills.Dockerfile,docker-compose.yml,.dockerignore— see the honesty statement below.package.jsonlintscript 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)CI runs exactly these three: the workflow calls the same
package.jsonscripts.i18n coverage — two numbers, both real
metadataForms.*— the Studio's own metadata forms (27 types).os linthides that bucket by design (its flag text: "the platform packages ship those translations"),os i18n extracthas--no-metadata-formsfor it, and the runtime already serves it:GET /api/v1/i18n/translations/zh-CNon this app carriesmetadataFormsfor 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.i18n checkhas 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 asos i18n checkcounts 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 ispnpm lint --i18n-strictin CI.ats_*object leaves + 34 app + 35 dashboard + 31 dataset leaves; azh-CNbrowser renders Chinese navigation, object, view-tab, column and option labels (screenshot 07).os validateaccepts list/form view names as_viewstargets (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 loadedBoot:
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.01-platform-home-overview.pngadmin@platform.example02-platform-hiring-funnel.pngadmin@platform.example03-hiring-home-overview.pngadmin@quillstone.example04-hiring-pipeline-kanban.pngadmin@quillstone.example05-hiring-interview-calendar.pngadmin@quillstone.example06-seeker-home-find-jobs.pngcandidate01@mail.example07-hiring-pipeline-kanban-zh-CN.pngadmin@quillstone.example, browser localezh-CNThe browser script lived in the scratchpad and is not committed.
⭐ Fresh clone:
pnpm install && pnpm dev— measuredEnvironment: 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 installwith a new, empty pnpm store and cache (--store-dir/--cache-dirpointed at fresh directories, so every package was downloaded), thenpnpm dev -p 3000 --log-level info— the README'spnpm devplus 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.pnpm install(cold store)pnpm dev→ seed complete.objectstack+distwiped,--database-driver memory.objectstack+distwiped, sqlite, warm node_modulesRun 1 is listed because it happened. It ran the README's exact
pnpm devat 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 forServer 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 afterpnpm dev; a stranger's clone → install → running demo is about 25 s here. Run 2 also proved the seed from outside:candidate01@mail.examplesigns 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:pnpm install --frozen-lockfile(exit 0),pnpm build(exit 0), andobjectstack startfrom the app directory with the artifact present — boots, 39 plugins,ats.rls-membership-resolverregistered withemployer_org_ids/applicant_candidate_ids, demo seed skipped underNODE_ENV=productionas designed;dist/objectstack.jsonalone in an empty directory with the same CLI (os start -a …, which is whatghcr.io/objectstack-ai/objectstackdoes) starts 38 plugins, andats.rls-membership-resolver/ats.demo-seed-gateare 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;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)
ats_inquiryas its heading and leaves the form title / section label untranslated #59 — the public application form page renders the object nameats_inquiryas its heading, drops the formdescription, and leaves the section label untranslated while translating the field labels (Console; measured in both locales).os i18n checkcounts the platform's 773 metadata-form keys against this app and double-counts action keys, so its percentage cannot be the release gate #60 —os i18n checkcounts the platform's 773 metadata-form keys against the app and double-counts action keys (CLI; the reason the raw percentage above is 38.9 %).AGENTS.mdand asrc/data/index.tscomment still say 801 seed rows; the loader, README and banner say 809 (the 8ats_inquiryrows Public application entry:ats_inquiry+ the public form, and make the guest grant real (ruling on #3's needs_decision) #37 added).Review notes
en.tsrestates 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 onlyzh-CNand letenbe the source labels, deletingen.tsand its line inindex.tsis the whole change;i18n checkreadsenas 100 % either way.pnpm lintgetting stricter is deliberate: from this PR on, a new field, option, view or navigation item lands with its Chinese label orpnpm lintfails. CONTRIBUTING says so.Generated by Claude Code