Skip to content

site(pricing): Enterprise is self-managed only; Business Self-Managed is its single-node entry (objectos#295 ruling A) - #257

Merged
hotlong merged 66 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1
Oct 15, 2026
Merged

hotlong merged 66 commits into
mainfrom
claude/pm-dispatch-objectos-ju9td1

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #234

Branch. This PR carries 4 commit(s) from the seat's linear stack, replayed onto main @ 0423fee. The replay is patch-equal to git diff 2e16f1f lin/issue-234, with byte-identical diffs. Head: 9a9cb7c. The staged SHAs quoted below are the pre-replay commits; their content is unchanged.

Brings www's pricing and edition copy into line with the maintainer's ruling on objectstack-ai/objectos#295 (2026-10-12, letter A, comment 6124045928):

  1. 「A 自托管统称 Enterprise」 — self-managed ObjectOS is ObjectOS Enterprise; src/lib/positioning.ts E1 is untouched.
  2. 「只自托管」 — Enterprise is self-managed only; Cloud tops out at Business.
  3. 「保留原名,归入 Ent」 — the single-node SKU keeps the name Business Self-Managed and its price ($540 / AI seat / yr, 10–25 AI seats); the copy describes it as the single-node entry option of ObjectOS Enterprise.

Prices and seat counts are byte-identical before and after. Only where the SKU sits moves.

src/lib/pricing.ts — every changed string, before → after

en

Where Before After Answer
billing note, 5th–6th sentence "Self-managed deployment starts on Business: Business Self-Managed is a single-node license for 10–25 AI seats at $540 per AI seat per year, bring your own model or key. ObjectOS Enterprise is the full private deployment, with offline and invoice payment." "The Cloud plans run up to Business; self-managed ObjectOS is ObjectOS Enterprise. The single-node entry option of Enterprise, Business Self-Managed, is a license for 10–25 AI seats at $540 per AI seat per year, bring your own model or key; beyond a single node, Enterprise is priced by volume, with offline and invoice payment." 1, 2, 3
Business card, 6th bullet "Self-Managed option: single node, 10–25 AI seats, $540 / AI seat / yr, bring your own model" removed from the Cloud card 2, 3
Enterprise card, 1st bullet "Full private deployment, including air-gapped" "Self-managed on your own infrastructure, including air-gapped" 2
Enterprise card, new 2nd bullet — "Single-node entry: Business Self-Managed, 10–25 AI seats, $540 / AI seat / yr, bring your own model" 3
Enterprise card, tagline "For larger organizations with custom security, deployment, and contracting needs." "For running ObjectOS on your own infrastructure, from a single node to air-gapped clusters." 2, 3

zh-Hans (zh-Hant is derived from it at build time by toHant, never hand-kept)

Where Before After Answer
billing note "自管部署从商业版起:商业版自管(Business Self-Managed)为单节点许可,10–25 个 AI 席位,每 AI 席每年 $540,自带模型或密钥;企业版为完整私有部署,支持线下与发票付款。" "云端套餐最高到商业版;自管部署的 ObjectOS 即 ObjectOS 企业版。企业版的单节点入门选项——商业版自管(Business Self-Managed)——为 10–25 个 AI 席位的许可,每 AI 席每年 $540,自带模型或密钥;单节点以上的企业版按量定价,支持线下与发票付款。" 1, 2, 3
商业版 card, 6th bullet "自管选项:单节点、10–25 个 AI 席位、每 AI 席每年 $540、自带模型" removed 2, 3
企业版 card, 1st bullet "完整私有部署,含离线隔离(air-gap)" "自管部署在你自己的基础设施上,含离线隔离(air-gap)" 2
企业版 card, new 2nd bullet — "单节点入门选项:商业版自管,10–25 个 AI 席位、每 AI 席每年 $540、自带模型" 3
企业版 card, tagline "面向有定制安全与合同需求的大型组织。" "面向自管运行 ObjectOS 的组织,从单节点到离线隔离集群。" 2, 3

The annual-billing sentence ("The headline price is billed annually (about two months free); monthly billing is also available." / "标价为年付价(约省两个月);也可选择月付。") sits in the same note and is owned by #214; it is untouched. The plan CTAs and ctaHref targets are owned by #215; untouched. #214 and #215 remain open.

Other locales. ja, de, es, fr and ko render the English copy (pricingCopy maps them to pricingEn; the page is noindexed and canonical to /en/pricing/). zh-Hant renders the s2t conversion of zh-Hans.

Design call: where the single-node bullet went

Both places. The Enterprise card gets the bullet, directly under the deployment bullet, so the two self-managed facts read together and "Volume AI-seat pricing" follows as the next tier; the billing note carries the long form (license, seats, price, BYO model, the volume tier above it). A reader scanning cards finds the entry price on the card that owns the SKU; a reader who wants the sentence finds it in the note.

Balance, measured on the built pages: bullets per card go from 4/5/6/5 (Free/Team/Business/Enterprise) to 4/5/5/6. The densest card moves one column right; the subgrid keeps all four cards the same height (684px en, 600px zh-Hans at every desktop width), and at phone widths the cards stack. The en bullet wraps to four lines on desktop (82px), the same shape the Business bullet had before.

The zh-Hans card bullet names the SKU as 商业版自管 without the Latin gloss: with "(Business Self-Managed)" inside the bullet, CJK line-breaking split the name at its hyphen on desktop ("(Business Self-" / "Managed),") and the bullet ran five lines (102px). Without the gloss it is three lines (61px); the note directly beneath still carries 商业版自管(Business Self-Managed), as it did before this card.

The Enterprise tagline

"For larger organizations with custom security, deployment, and contracting needs." sat above a card whose first two bullets now say self-managed only and a 10–25-seat single-node entry. It now says what the edition is under answers 2 and 3: ObjectOS run on your own infrastructure, from one node to air-gapped clusters.

Length was measured against the built page before choosing. The tagline is a shared subgrid track, so the longest tagline sets the price row for all four cards. The seat's proposed en copy ("For organizations that run ObjectOS on their own infrastructure, from a single node to air-gapped clusters.") wraps to four lines at 1366–1920 (the grid is capped at 1120px, so every desktop width measures the same) and moves the price row from 525px to 547px in all four cards; so does every variant that keeps "organizations" together with "own infrastructure" and "air-gapped clusters". The chosen en copy keeps those two phrases verbatim, stays at three lines like the tagline it replaces, and leaves the price row at 525px and the cards at 684px. The seat's zh-Hans copy ("面向在自有基础设施上运行 ObjectOS 的组织,从单节点到离线隔离集群。") goes from two lines to three; the chosen zh-Hans copy keeps 组织, 自管 and the single-node-to-air-gap range at two lines, price row unchanged at 475px and the cards at 600px.

Sweep of src/ and content/

git grep -n -i -E "self-managed|private deployment|自管|私有|dedicated cloud" over src/ and content/. Every hit that named an edition and listed Business Self-Managed and Enterprise as sibling self-managed editions, gave Business a self-managed delivery, or used "ObjectOS Self-Managed" as an edition name now reads the ruling's way:

File Before After
content/pages/en/ai.ts table "ObjectOS Business (Cloud or Self-Managed)" · "ObjectOS Enterprise" / "Same, plus private deployment and bring-your-own-model" "ObjectOS Business (Cloud)" · "ObjectOS Enterprise (self-managed)" / "Same, on your own infrastructure, plus bring-your-own-model"
content/pages/en/ai.ts models FAQ "Business Self-Managed and Enterprise private deployments bring your own model or key." "Self-managed ObjectOS Enterprise, from its single-node Business Self-Managed entry up, brings your own model or key."
content/pages/zh-Hans/ai.ts table "ObjectOS 商业版(云端或自管)" · "ObjectOS 企业版" / "同上,另支持私有部署与自带模型" "ObjectOS 商业版(云端)" · "ObjectOS 企业版(自管)" / "同上,部署在你自己的基础设施上,另支持自带模型"
content/pages/zh-Hans/ai.ts models FAQ "商业版自管与企业版私有部署自带模型或密钥。" "自管部署的 ObjectOS 企业版(含单节点入门选项商业版自管)自带模型或密钥。"
content/pages/en/platform.ts open-source FAQ "— on Cloud, Business Self-Managed, and Enterprise." "— on Cloud and on Enterprise, from its single-node Business Self-Managed entry up."
content/pages/en/platform.ts own-infrastructure FAQ "ObjectOS Business Self-Managed is a licensed single-node deployment that puts the in-app AI on your own infrastructure, bring your own model or key. ObjectOS Enterprise is the full private deployment — a VPC, local servers, or an air-gapped network — with clustering, …" "Self-managed ObjectOS is ObjectOS Enterprise. Its single-node entry option, Business Self-Managed, is a licensed deployment that puts the in-app AI on your own infrastructure, bring your own model or key. Beyond a single node, Enterprise runs in a VPC, on local servers, or in an air-gapped network, with clustering, …" (still "three ways")
content/pages/zh-Hans/platform.ts "在 Cloud、Business Self-Managed 与 Enterprise 上提供" · "ObjectOS Business Self-Managed 是带许可的单节点部署…ObjectOS 企业版是完整私有部署——VPC、本地服务器或隔离网络——…" "在 Cloud 与 Enterprise(含单节点入门选项 Business Self-Managed)上提供" · "自管部署的 ObjectOS 即 ObjectOS 企业版:其单节点入门选项 Business Self-Managed 是带许可的部署…;单节点以上,企业版部署在 VPC、本地服务器或隔离网络中,…"
content/pages/{en,zh-Hans}/data-modeling.ts "— on Cloud, Business Self-Managed, and Enterprise." / "在 Cloud、Business Self-Managed 与 Enterprise 上提供" "— on Cloud and on Enterprise, from its single-node Business Self-Managed entry up." / "在 Cloud 与 Enterprise(含单节点入门选项 Business Self-Managed)上提供"
content/pages/en/forward-deployed-engineers.ts lead · FAQ "self-managed (Business Self-Managed, Enterprise) delivery" · "self-managed delivery (Business Self-Managed on a single node; Enterprise for clustering, air-gapped operation, and SCIM)" "self-managed (Enterprise, from its single-node Business Self-Managed entry up) delivery" · "self-managed Enterprise delivery (Business Self-Managed as its single-node entry; clustering, air-gapped operation, and SCIM beyond it)"
content/pages/zh-Hans/forward-deployed-engineers.ts lead · FAQ "自管(Business Self-Managed、Enterprise)交付" · "自管交付(Business Self-Managed 单节点;Enterprise 提供集群、离线隔离与 SCIM)" "自管(Enterprise,含单节点入门选项 Business Self-Managed)交付" · "自管的 Enterprise 交付(单节点入门选项 Business Self-Managed;单节点以上提供集群、离线隔离与 SCIM)"
content/pages/en/trust-center.ts boundaries "On ObjectOS Enterprise, the runtime … stay inside that boundary. Business Self-Managed runs its single-node license on your infrastructure too. ObjectOS Cloud is operated by us." "On ObjectOS Enterprise — from its single-node entry option, Business Self-Managed, up — the runtime … stay inside that boundary. ObjectOS Cloud is operated by us."
content/pages/en/trust-center.ts license check "ObjectOS Self-Managed validates its license online (Enterprise air-gapped licenses are offline-validated)." "Self-managed ObjectOS Enterprise validates its license online (air-gapped Enterprise licenses are offline-validated)."
content/pages/zh-Hans/trust-center.ts boundaries "在 ObjectOS 企业版中,…都留在这个边界内。商业版自管同样把单节点许可运行在你的基础设施上。ObjectOS Cloud 由我们运营。" "在 ObjectOS 企业版中——从其单节点入门选项商业版自管(Business Self-Managed)起——…都留在这个边界内。ObjectOS Cloud 由我们运营。"
src/lib/clusters.ts definition · FAQ "licensed self-managed ObjectOS — Business Self-Managed or ObjectOS Enterprise — which adds…" · "Self-managed ObjectOS is licensed: Business Self-Managed is a single-node delivery with…; ObjectOS Enterprise is the full private deployment with clustering, …" "licensed self-managed ObjectOS — ObjectOS Enterprise, from its single-node entry option Business Self-Managed up — which adds…" · "Self-managed ObjectOS is ObjectOS Enterprise, licensed: its single-node entry option, Business Self-Managed, brings … to one node; beyond that, Enterprise adds clustering, …"
src/lib/cluster-i18n.ts zh FAQ "自管部署的 ObjectOS 是付费许可的:Business Self-Managed 是单节点交付,…;ObjectOS 企业版是完整私有部署,提供集群、…" "自管部署的 ObjectOS 即 ObjectOS 企业版,按许可付费:其单节点入门选项 Business Self-Managed 在单个节点上提供…;单节点以上,企业版再提供集群、…"
src/pages/llms.txt.ts Pricing line "…the ObjectOS Cloud plans (Free, Team, Business), Business Self-Managed, and ObjectOS Enterprise." "…the ObjectOS Cloud plans (Free, Team, Business), and self-managed ObjectOS Enterprise, from its single-node Business Self-Managed entry up."

Left alone, with the reason: src/lib/positioning.ts E1 (the ruling keeps it); src/lib/security.ts, src/lib/home.ts, content/pages/*/trust-center.ts:19 and :49 (zh), src/lib/clusters.ts:147 and src/lib/legal.ts (lower-case "self-managed" / 自管部署 / 私有部署 describing a deployment, or already "ObjectOS Enterprise"); every blog post (the hits describe deployments — "managed cloud or private deployment", "self-managed (ObjectOS Enterprise)", de "Cloud oder Self-Managed" as a delivery pair — and none names Business Self-Managed; editing published posts would also be an updated bump outside this card); the zh glossary hits (私有 as "proprietary"). Each file keeps its existing spelling of the SKU name (Latin "Business Self-Managed" in platform/data-modeling/FDE/cluster zh copy, 商业版自管 in ai/trust-center/pricing zh copy).

Consistency with docs.objectos.ai

objectos#295's execution PR has not landed: the card is pm:queue and no open objectos PR touches content/docs/resources/license.mdx. Quoted from objectos origin/main @ 76988da, the lines that contradict this copy today and that the ruling's execution list names as changing:

  • :8-10 "sold in two deliveries: ObjectOS Cloud (we operate it) and ObjectOS Self-Managed (you operate it…)" — answer 1 renames the second delivery ObjectOS Enterprise (www's E1 already said so before this card).
  • :40 Business delivery "cloud or self-managed (single-node)" and Enterprise delivery "cloud or private deployment" — answer 2 makes Enterprise self-managed only and answer 3 moves the single node to Enterprise; :41 Business price cell "self-managed: $540 / AI seat / yr" moves with it; :45 "priority (cloud) / standard (self-managed)" follows.
  • :64-69 "Business Self-Managed — the Business plan delivered as a single-node Docker license…" — answer 3 re-describes it as Enterprise's single-node entry option; :70-77 Enterprise "full private deployment for everything beyond that" becomes self-managed wording.
  • :87 scenario row "Business Self-Managed" and :142 "self-managed ObjectOS (Business Self-Managed or Enterprise) is licensed", :148 FAQ — the sibling form this PR removes on www.

Nothing in this PR contradicts the ruling's target state; the facts the two sites share (name, $540 / AI seat / yr, 10–25 seats, BYO model, never bricks, offline and invoice payment on Enterprise) are unchanged on both.

Gates (final head 36bab9a; the same four gates were also green at a3ed5e4 before the tagline commit)

Gate Exit Key line
pnpm content:lint 0 content lint passed (347 files, 44 glossary terms checked)
pnpm check 0 154 files: 0 errors, 0 warnings, 0 hints
pnpm build 0 888 page(s) built; content lint --dist passed, CJK line breaks 0 segment breaks, quotation marks 0 wrong, 94112 internal links resolve; git status clean after the build
pnpm seo:smoke 0 878 HTML pages checked; 577 sitemap URLs against 301 noindexed pages; 16317 prose blocks language-checked

Browser

Playwright 1.56.1 + Chromium 141 at /opt/pw-browsers/chromium against dist/ served on 127.0.0.1. /en/pricing/, /zh-Hans/pricing/ and /ja/pricing/ (en fallback) at 1366×625, 1440×750, 1512×830, 1920×945, 390×844 and 360×800, light and dark (localStorage theme plus prefers-color-scheme emulation): 36 shots, 36 pass — HTTP 200, html[data-theme] equals the requested theme, scrollWidth equals innerWidth (no horizontal overflow). Looked at: four equal-height cards at every desktop width with Enterprise the densest; single column at 390/360 with the note wrapping cleanly; dark-theme muted text and the accent checkmarks read fine; the zh-Hans hyphen break is gone after the gloss was dropped. (Playwright's element crop of the plan grid at 1366×625 can come out stitched short at its foot because the grid is taller than the viewport; the full-page shots are the canonical artifacts.)

After the tagline commit (36bab9a): en and zh-Hans at 1366×625, 1920×945 and 360×800, light and dark — 12 shots, 12 pass on the same assertions. Measured on the built page: en tagline track 67px (three lines, unchanged), price row at 525px in all four cards, cards 684px; zh-Hans tagline track 45px (two lines, unchanged), price row 475px, cards 600px; at 360 the Enterprise card is 573px en / 530px zh-Hans with the tagline at three / two lines. Looked at: the price amounts and the four CTAs sit on one line across the row at both desktop widths, the tagline reads in full with no orphaned word, and nothing else moved relative to the earlier shots.

Acceptance notes

  • The Enterprise tagline's "custom security … contracting needs" phrasing is gone; the contracting facts (offline and invoice payment, volume pricing, SLA) stay in the Enterprise bullets and the billing note.
  • Blog posts that say "self-managed (ObjectOS Enterprise)" already agree with the ruling and were not touched.

Generated by Claude Code

🤖 Generated with Claude Code

https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr

claude added 30 commits October 5, 2026 12:39
… definition post (slot 2026-10-08)

Post 1 of the positioning wave: the executable business ontology defined
from the objectstack README and the Business Ontology concepts page,
quoted rather than paraphrased — the six-kind core mapped onto the
bundled CRM example, the four projections with a removal test to tell
core from projection, the three things it is not, where code goes, and
the four promises led by Executable. ObjectOS appears only as the
canonical T4 + E1. Eight locales (zh-Hant generated by gen:zh-hant), one
cover and two diagrams, in English text and in Chinese text for the
zh-Hans/zh-Hant pair.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The post was written for the Thursday 2026-10-08 slot and dated for it,
but it was never merged that day, so the slot passed with the post still
unpublished. Under the publishing-cadence rules in AGENTS.md, a post's
`date` is its planned slot and merge time is publish time; the
forward-only rule protects the dates of posts that are already live,
and this one is not. The seat therefore moves it to the next free slot,
today, Thursday 2026-10-15, and merges it today.

Only the `date` frontmatter line changes, in the seven hand-maintained
locales (en, zh-Hans, ja, de, es, fr, ko), bare-date format kept and no
`updated` added; zh-Hant is regenerated from zh-Hans with
`pnpm gen:zh-hant`. No other file changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Certification, AI governance tiers, MCP setup, templates, Cloud data,
Business Self-Managed, and two locale overstatements, each re-sourced
from objectos and objectstack docs at origin/main:

1. Enterprise card: "SCIM + compliance (SOC 2 / ISO)" -> "SCIM, compliance
   attestations" (license.mdx:72-73; security.mdx:142 says ObjectOS itself
   is not certified).
2. AI page: governance claims written per edition (license.mdx:42-43):
   a taste on Free, in-app AI on Team, AI action audit / guardrails /
   permission scoping from Business. The "0 bypasses" tile, the Ask audit
   item, the "never how it is governed" item, the edition table and the
   two FAQs now say that.
3. MCP page: the npx/--url snippet (no bin, no such flag) is replaced by the
   documented `claude mcp add --transport http` command and the
   `{"type":"http","url":".../api/v1/mcp"}` client config
   (connect-mcp.mdx:26-32, 85-94).
4. Templates page: the real eight-template catalog (todo, contracts,
   procurement, compliance, helpdesk, content, hr, project;
   templates.mdx:19-28) replaces the six invented starters; the decision
   table now carries the documented install / fork / publish paths
   (templates.mdx:33-62). llms.txt follows the page description.
5. Trust center: the lead quotes E1 (Cloud / self-managed split), the
   "Self-hosted" tile becomes "2 editions", the data-residency and
   licence-check items follow index.mdx:63-77 and license.mdx:150-152,
   and the model-training FAQ says only what the Privacy Policy says
   (legal.ts:414-415).
6. Pricing: Business Self-Managed (single node, 10-25 AI seats,
   $540 / AI seat / yr, bring your own model; license.mdx:41, 64-69) is a
   labelled option under Business, the billing note no longer says
   self-managed is Enterprise-only, and the Business governance line names
   the AI governance row. Team/Business headline prices untouched (#214).
7. zh home tile "数据不出网" -> "数据驻留" (= en "Data residency"); ja
   security tile "0 既定の外部テレメトリ" -> the en "Agent inherits user
   identity" tile.

zh-Hans changed with en; zh-Hant for marketing pages and pricing derives
at build time (registry deepS2t / toHant), so no generated file changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Article tables on a phone no longer run each row onto one line. Cells
wrap, each column keeps a 6.5rem floor (three columns fit the 328px
column of a 360px phone), and overflow-wrap: anywhere keeps an
overflowing table at exactly its column count x 104px. The widest
English table goes from 2,035px to 328px at 360x800; the 12 tables with
four to six columns still scroll, to at most 625px, with the always-on
scrollbar and trailing-edge fade they already had.

Code blocks get the same two scroll affordances at every width, since
they overflow at desktop sizes too. The marketing decision table drops
its 760px floor on a phone and takes the same wrap, floor and
affordances.

Every scroll container is keyboard-reachable and named: post tables get
tabindex="0" and aria-labelledby their section heading (the article
title when no heading precedes them); code blocks keep Shiki's tabindex
and gain role="group" with a "Code (lang)" name; the decision-table
wrapper becomes a named, focusable group. group, not region, because a
region is a landmark and duplicate names fail axe landmark-unique.

Body images become links to their own asset: one tap opens the diagram
in the browser, where pinch zoom takes its smallest text past 11px. The
link's name is the image's alt text. No SVG changes; desktop geometry of
tables and images is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The marketing artifact code block is a scroll container; it gets
tabindex="0" and role="group" named by its section heading, like the
other scroll containers, which clears the last axe
scrollable-region-focusable finding on the marketing pages.

The security page's data-residency table had the same 620px floor as the
decision table, which put its third column ("Leaves boundary?") wholly
off-screen on a phone. It takes the same phone block: cells wrap, a
6.5rem column floor, tighter padding, type size and tracking, and the
scrollbar and trailing-edge fade if it still overflows. Its wrapper is
focusable and named by the section heading. Desktop is unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…e build

Six visible defects from the 2026-10-06 site audit, plus the gate that
would have caught the broken links.

- Home "Latest insights": the cover's height attribute became CSS
  height:500px and beat aspect-ratio, cutting each 16:9 cover to a 344x500
  sliver. height:auto, a 16:9 box (the covers' own ratio), contain for the
  odd 16:10 cover.
- Blog index: the feature card spanned three rows beside three cards
  (~1470px) with ~670px of content, leaving an ~800px band. It now takes
  its own row, cover beside text from 1024px, the other three below.
- 404: a top-level 404.html (Cloudflare Pages otherwise runs the site as
  an SPA and answers every missing URL 200 with the language redirect) and
  a localized one per locale at <locale>/404.html, moved there by a small
  build hook. noindexed, no canonical, out of the sitemap.
- Cluster pages linked topic hubs not built in de/es/fr/ja/ko. They now
  link the hub really built (this locale's, else English), and
  content-lint --dist fails on any internal link to a page the build did
  not produce.
- Footer links and the drawer's close label come from the i18n copy.
- Alignment: .item > span; align-content:start on capability cards and
  next-page links; no min-height floor on template/compare cards; pricing
  cards on a subgrid; footer brand nowrap; the phone rule now clears the
  odd capability items' border; non-breaking space before card arrows.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
The browser check of the German drawer showed a visible English "Theme"
button beside the localized menu, the same defect as the footer links and
the drawer's "Close" label: hard-coded English in the shared chrome. The
theme toggle's name and tooltip, the language switcher's "Language:"
prefix, the three nav landmark names and the home page's topic-cluster nav
name move into the i18n copy for all eight locales. English output is
byte-identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…e to 4.5:1, fix three near-misses

The 11 text lines the audit measured running past their cards, now inside
them with room left for wider system faces:

- self-hosted-runtime-en: the Runtime and AI Agent subtitles and the
  "Only approved minimum context is sent" line wrap to two lines (the
  Runtime line no longer runs over the green connector).
- ai-wrote-your-app-dare-to-merge cover (en and zh): the two captions beside
  the Merge pills wrap to two lines centred on the pill; "+8,142 -0" is
  right-aligned 22px inside its panel in both locales.
- ai-ontology-open-protocol cover-en: the two dark pills widen to 264px and
  the lock moves to the panel's lower right.
- tool-call-lifecycle-en: card 1 rewrapped ("change as Zhang, not as / an
  anonymous superuser").
- natural-language-to-app-metadata cover (en and zh): the spec card widens
  to 350px with 282px pills and sits 14px lower, so it no longer clips the
  subtitle's descenders; the arrows shorten to keep their gaps.
- one-definition-four-surfaces-en: the audit line is reworded to fit.

Contrast: defect-layers-en .none goes from #8a9c95 (2.7:1) to the house
.soft #5d736b.

Near-misses: review-gate-doors-en gives the gap card 12px more height and
the 29/16 numerals 6px more air; self-hosted cover paints the ObjectOS label
after the connectors (both locales), as the runtime diagram already does;
review-loop centres its card row (40px each side, both locales) so card 4's
shadow is no longer clipped.

Also objectos-automation-engine cover-en: the run's meta line ran 40px
under the "selected" tile, hiding "runAs: user"; it drops the "duration:"
label to clear the tile.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…their alt text

Four English and five zh-Hans posts used AI-illustration webp images whose
alt text was only "Concept diagram" / 「概念示意图」. Each alt now says what
the picture shows (customer hub, read-only connection into a governed object
layer, tangled vs. modelled architecture, factory systems feeding an object
layer, legacy stack to structured model). The images are unchanged.
zh-Hant regenerated with pnpm gen:zh-hant.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ed over it

scripts/svg-text-fit.mjs measures every <text> in content/blog's SVGs and
fails when its box runs more than 2px past the filled shape under its centre,
or when a filled shape painted after it covers more than 2px of it both ways.
The #193 sweep only caught overflow that also dropped contrast, and a
tightest-rectangle measure sees an overflowing line's whole background as
its container; this gate measures each line against the shape it sits on.

It runs without a browser, since CI installs none: text is laid out with
Arial advance widths (scripts/lib/svg-text-metrics.mjs, generated from the
metric-compatible Liberation faces by scripts/gen-svg-text-metrics.mjs) and
the browser's text-box rules. Against headless Chromium on the same files
it agreed to within Chromium's whole-pixel rounding and found the same
crossings. A deliberate crossing goes in the documented allowlist; a stale
entry fails the gate.

Wired into `pnpm build` after content-lint --published (so CI's Build step
runs it) and exposed as `pnpm svg:lint`.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Measured with DejaVu Sans (the default sans-serif on Debian/Ubuntu
desktops, about 15% wider than Arial), two of the refitted lines still ran
past their shapes: the spec pills on the natural-language cover widen to
290px (both locales), and tool-call-lifecycle card 1 keeps its original
words, rewrapped as 'change as "Zhang"—not / an anonymous superuser'.

The gate's header states its Chromium agreement as "2px", not "2.0px".

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…E1 attribution across 18 posts

Post-side update sweep for the 2026-10-03 positioning (work-order item 3):

- CTA: the stale `npm i -g @objectstack/cli && os start` block in 15 en
  posts and their authored locales becomes the objectstack README path —
  `npm create objectstack@latest my-app && cd my-app`, point a coding
  agent at it, `npx os dev --ui`. `how-to-self-host-objectstack` keeps
  `os start` (the production server), as does the mid-article operator
  line in `enterprise-ontology-race-open-vs-closed`.
- Descriptor: "the commercial production platform" / 「生产平台」 and the
  ja/de/es/fr/ko equivalents in ai-ontology-open-protocol,
  beyond-agentforce-copilot-open-runtime,
  enterprise-ontology-race-open-vs-closed, forward-deployed-engineer-tools
  and self-hosted-ai-app-platform become positioning[locale].descriptor
  verbatim ("the commercial runtime environment built on ObjectStack").
- Attribution: extend-existing-systems-with-ai scopes "ObjectOS runs in
  your environment" to self-managed (ObjectOS Enterprise) or the open
  runtime per E1 and objectos license.mdx (Cloud operates the model);
  objectos-action-tools attributes the Action mechanism (opt-in exposure,
  confirmation, permissions, audit) to the open ObjectStack runtime and
  positions ObjectOS with the descriptor. objectos-agent-permission-
  boundaries already attributed everything to ObjectStack; untouched.
- `updated: 2026-10-06T10:00:00+08:00` on every corrected file; no `date`
  touched, no slug changed. zh-Hant regenerated with gen:zh-hant.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ullet matches the FAQ

The bullet ended "Business data and prompts never leave your perimeter",
which is false once an external AI provider is configured — the FAQ
answer in the same post already says so. It now states the same claim
as the FAQ: business data stays in your database; prompts go only to the
AI provider you configure (on ObjectOS Cloud the model is operated for
you); permissions decide what the model sees. en and zh-Hans carried the
false sentence; ja, de, es, fr and ko had no sentence about prompts and
now carry the corrected claim too. zh-Hant regenerated.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…re menus, tap targets

Contrast. --muted is #626c66 light (5.43:1 on --surface) and #8a968d dark
(5.42:1); the light accent moves to #3d58f0 (4.67:1 on --surface-sunk);
the focus ring is the solid accent (5.44:1 light, 7.06:1 dark) instead of
a 28% tint (1.5:1). The home, evidence, security, glossary and cluster
accents get dark-theme overrides (green #3fbf8f, blue #8fa3ff, amber
#e3a54a, red #f08a8a, all >= 6.9:1 on the dark surface) and the light
amber darkens to #93600f (5.34:1). The status pill reads theme tokens
(published 5.03:1 light, 6.19:1 dark on its tint), the pricing Team CTA
is --surface on --accent (7.06:1 dark, was 2.36:1), and code blocks use
Shiki's github-dark-default (comments 6.15:1, were 3.04:1).

Keyboard. The mobile drawer moves focus to its close button, makes the
page behind it inert, keeps Tab inside it and returns focus to the menu
button on close. The header dropdowns drop role=menu/menuitem for the
disclosure pattern (aria-controls, plain links, Escape returns focus,
focus leaving a menu closes it).

Tap targets. The hamburger, drawer close, header CTA and wordmark are
44px tall below 900px; footer links 24px (44px on a phone); the glossary
and post back links 44px without moving the text; card title links cover
their cards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…posts

`[ObjectOS](https://www.objectos.ai)` in business-app-in-16k-tokens (1 per
locale) and forward-deployed-engineer-tools (2 per locale) pointed at the
root language-redirect page, so every click took a meta-refresh hop before
landing. Each locale now links its own home (`/en/`, `/de/`, ...); zh-Hant
regenerated with gen:zh-hant (it rewrote `/zh-Hans/` to `/zh-Hant/`). 24
links in 16 files; no `date` or `updated` touched — a link target is not a
substantive revision.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…hubs, OG

The site-wide audit on #219 measured, on a build of this tree's base:

- Sitemap: 190 noindexed fallback pages listed. The sitemap filter now
  reads each built page and lists it only when it is indexable and its own
  canonical (astro.config.mjs) — one rule for every fallback producer
  instead of a second copy of each registry.
- Pricing/privacy/terms/refunds in ja/de/es/fr/ko rendered the English
  copy, self-canonical and in every locale's hreflang (20 pages); security
  in those five locales inherits five English sections. They now follow
  the marketing fallback rule through src/lib/fallback.ts: noindex,
  canonical to /en/, hreflang only for authored locales, the switcher
  still offering every locale. Authored-ness is derived, not listed: a
  copy table entry that is the English object is a fallback, and a
  security locale that inherits any English field is one.
- Base takes `contentLocale`: a fallback body gets `<main lang="en">`
  (marketing, glossary, pricing, legal; 210 pages had English under
  lang="de"/"ja"/...), og:locale follows it. The security page marks each
  inherited English part `lang="en"`; glossary fallbacks mark the
  reader-locale article links with their locale.
- The 35 non-English cluster pages are one template per locale: noindex,
  canonical to the English cluster, out of hreflang and llms.txt.
- Pillar pages: every footer links the five clusters (FooterClusters),
  and each post a cluster lists as its reading path links back to it.
- Topic hubs listing fewer than 3 posts (51) are noindexed and out of
  hreflang; every hub gets BreadcrumbList and ItemList JSON-LD and a
  "<topic> · Articles · ObjectOS" title.
- Root `/`: absolute hreflang, canonical /en/, no noindex beside the
  meta refresh. SecurityPage links its post with the trailing slash.
- public/_headers caches the hashed /_astro/* files as immutable.
- og:locale uses en_US/zh_CN/zh_TW/...; og:image:alt everywhere and
  width/height when known; Organization gets @id and sameAs (the GitHub
  organization only); SoftwareApplication gets offers from the pricing
  page's own dollar plans.
- Titles: marketing titles that already name ObjectOS no longer append
  " · ObjectOS" (27 indexable doubles); the blog index takes the home
  page's heading for it instead of "Articles · ObjectOS".

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… lint warns at 60/160

seo:smoke reads every built page once and asserts invariants over the
whole build instead of per-registry URL lists, so a new fallback producer
that gets the rule wrong fails without this script learning about it:

1. every sitemap URL is a built, indexable, self-canonical page;
2. every canonical and hreflang target is indexable and self-canonical,
   and a page that canonicalizes elsewhere is noindexed;
3. every block of prose in <main> is written in the language its nearest
   `lang` declares (scripts/lib/text-lang.mjs: script for CJK, distinctive
   stopwords for en/de/es/fr; too little text is "cannot tell", never a
   pass), with a per-locale control so it cannot measure nothing;
4. no internal link, canonical, og:url or hreflang redirects (missing
   trailing slash, `/`, `.../index.html`, a `_redirects` source).

Plus the root page (canonical /en/, absolute hreflang, no noindex beside
the refresh), `_headers` (rule present, every /_astro/ name hashed),
og:locale values, og:image:alt, the Organization @id/sameAs, hub
BreadcrumbList/ItemList, and the clusters: English in llms.txt and the
sitemap, the templated locales noindexed and canonical to English.

On the base build this reports 190 noindexed sitemap URLs, 215 pages
whose <main> is in another language than declared (25 indexable), and 9
redirecting link targets on 24 pages; on this branch's build it passes.

content-lint: a title wider than 60 columns or a description wider than
160 (CJK counted double) is a warning. Posts first published before the
check are counted in one summary line; `--snippets` lists them all.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…uled

The home page's SoftwareApplication listed Offers derived from the pricing
page ($20/$45 per AI seat billed annually). That price is under a pending
maintainer decision (www#214: $20/$45 annual on www vs $24/$54 monthly on
docs), so structured data must not assert either until it is ruled. The
rest of the item-10 changes stay: the publisher still references the
Organization @id.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ith srcset

The post header cover is the LCP element on a phone, and 80 posts used a
1.5-1.9 MB PNG for it (ai-agent-workbench: 9.96 s LCP at 390x844, 4x CPU,
slow 4G). Card thumbnails on the blog index, topic hubs and the home page
downloaded the same full-size file, about 18.8 MB for one scroll of
/en/blog/.

A PNG or JPG cover now goes through <Image> as WebP with a width list and
`sizes` per slot:

- post header: 480-1600w, `(min-width: 908px) 860px, calc(100vw - 2rem)`;
  still eager with fetchpriority=high, since it is the LCP element;
- ArticleCard: 360-1280w, sized for the three-column grid, the two-column
  tablet grid and the phone column; the feature card's cover is eager, as it
  opens the blog index above the fold;
- the home page's latest-posts grid: 360-1080w.

The ai-agent-workbench cover at 1280w is 58 KB (the original is 1.62 MB);
a 1:1 crop of the 1280w WebP against the original resized to 1280 shows no
visible loss (PSNR 39 dB). SVG covers are a few KB and scale themselves, so
they keep a plain <img> on the original.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
209 posts (41 English) named an SVG as og:image, twitter:image and
BlogPosting.image, and X, LinkedIn, Facebook and Slack draw no card for an
SVG; the PNG-cover posts named a 1.5-1.9 MB 1672x941 file.

`scripts/og-images.mjs` runs in `pnpm build` after `astro build` and
renders one 1200x630 JPEG per cover into dist/og/blog/<post>/. The URL rule
lives in scripts/lib/og-image.mjs, shared with BlogPost.astro, which now
names that file in og:image, twitter:image and BlogPosting.image, with
og:image:width/height 1200x630 and the post title as og:image:alt. The URL
carries a hash of the cover's bytes, so an edited cover gets a new URL the
networks re-fetch. The step is driven from the built HTML and fails the
build when a page names a share image no cover maps to, or declares a size
that is not the file's.

SVG text: sharp draws it through librsvg/Pango/fontconfig, which use the
machine's fonts. The covers ask for -apple-system, Segoe UI, Inter, Arial
and Menlo, and CI's ubuntu-latest has no CJK font, so the Chinese covers
would have shipped as rows of boxes. The rasterizer now sees only bundled
OFL faces (scripts/lib/og-fonts.mjs): Arimo and Cousine (the Arial and
Courier New metrics `pnpm svg:lint` measures with), Noto Sans SC, Noto Sans
Symbols 2, Noto Sans Math and Noto Emoji. Fontsource ships them as WOFF2,
which the FreeType in sharp's libvips cannot read (every glyph drew as a
box), so wawoff2 decodes them to TrueType in a temp dir that a generated
fonts.conf points at. Before rendering, every character in each SVG cover's
text is checked against the faces' cmaps; one that no face has fails the
build with the cover, code point and line. On this tree that check first
found U+21B6 and U+2298, which is why Noto Sans Math is in the set.

An SVG cover is drawn whole at 1120x630 and widened by repeating its edge
columns (two covers put a caption in the bottom 23px a centre crop would
cut; no cover draws in its outer 40px). A PNG/JPG cover is an illustration
without text and is centre-cropped. Rendering SVG text on several libvips
threads in one process crashed (SIGSEGV or "SVG rendering failed" in 4 of 6
runs), so SVGs are rendered in three worker processes, one at a time each.

Result: 87 images, 5.2 MB, 12 s of the build; zh-Hans, zh-Hant, ja and ko
rasters looked at, no missing glyph.

New devDependencies, all build-time only: wawoff2 (MIT) and six Fontsource
packages (OFL-1.1). sharp was already a dependency.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…, dim it in dark mode

The 1,732,355-byte hero PNG was loaded eagerly by the 120 marketing
evidence pages, where it renders in a 415x340 box, and lazily by the home
page; it was also the default og:image of every page without its own.

- src/assets/objectos-home-hero.png (moved from public/images/) goes
  through <Image> as WebP with srcset (400-1586w). The box crops the 1.6:1
  image with object-fit: cover, so `sizes` asks for the drawn width, not the
  box's: 545px on a desktop, about 1.2x the box on a phone.
- Marketing evidence pages: measured at all six AGENTS.md viewports, the
  hero's top is at 172px on a desktop (the LCP element there) and at
  713-781px on an 800-844px phone, so it is in the first screen everywhere
  and stays eager, now with fetchpriority=high. Home: it sits under the
  hero and the video at every viewport, so it stays lazy.
- Dark theme: the white render was a glaring block on the dark page. There
  is no dark variant of the render, so the image itself is dimmed
  (brightness 0.6, contrast 1.12; three strengths compared side by side in
  the hero panel). No layout CSS changes.
- Base.astro: the default share image was the 1586x992 PNG by its public
  path, which the move removes. It is now getImage() of the same asset, a
  1200x630 JPEG (94 KB). og-images.mjs now checks every og:image that is a
  file of this site against the size its page declares, the default
  included, and fails on one that is not in dist/.

The hero component code no longer reads the import's width, so Astro drops
the 1.7 MB original from dist/ (nothing references it).

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
claude added 26 commits October 15, 2026 12:30
…xport

`pnpm wechat` / `wechat:draft` render through marked, not Astro, so neither
the rehype step that removes a line break between two CJK characters nor the
quote step reaches the export. The four hard-wrapped zh-Hans posts exported
with 20, 40, 23 and 18 such breaks (counted by the `content:lint --dist`
scanner), which the WeChat reader shows as spaces mid-sentence, and marked
leaves ASCII quotes straight.

`toWechatHtml` now walks marked's tokens (`walkTokens`) and, per paragraph,
heading, list-item text and table cell, applies `collapseCjkSegmentBreaks`
with neighbours read across bold, emphasis and links, and `directQuotes` when
the block holds a CJK letter. Code spans, images and raw HTML bound the first
and are opaque to the second; fenced code is never seen. An export of the four
posts now differs from before only by the 101 removed breaks and six straight
pairs curled “ ”, all opening marks first; an English export is byte-identical.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
#226's quote step ran on zh, ja and ko only. The same fault shows in the
Latin locales, because smartypants reads a document as one string: a
heading or table cell that opens on a quote takes its direction from the
block before it, so an English table printed ”What exists in our
business?”, and an elided century came out as Next ‘26. German types
„Wort" and smartypants curls the ASCII closer as the English ”, which German
never prints after „. The built site had 366 such marks on 29 pages: de 344
on 17, en 16 on 7, es 5 on 4 and fr 1 on 1.

The step is now `remarkQuotes` and runs on every Markdown and MDX file. It
keeps #226's rule: in a paragraph, heading or table cell, the first mark of a
pair opens and the next closes, and an apostrophe, including '26, is ’. It
adds one rule with no per-locale branch: a low mark („ or ‚) only opens, and
its pair closes with “ or ‘. The author's choice of mark is kept, so « » and
„ “ stay as typed, and an ASCII pair stays “ ” as smartypants prints it. Code
is opaque, as before.

`content:lint --dist` now reads every built page, not only zh, ja and ko,
with the same rule, including the low-mark pairs. Its summary lists the
pages and marks it read per `<html lang>`. A build with no page, or no
curly mark, fails as unmeasured.

The whole-site byte diff shows exactly those 366 characters on those 29
pages. Each one is a quotation mark replaced by another, outside tags and
outside code. No zh, ja or ko page changes.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…strings, scope the existing-systems claim, README agent path, per-page edition answers

Every non-blog page now reads what ObjectStack and ObjectOS are from
src/lib/positioning.ts and from public sources only (objectstack README
at origin/main 6befe19c; docs.objectos.ai license, extend-existing-systems,
ai-builder and configure/ai pages).

1. Token figures retired on ai, agent-developer, forward-deployed-engineers,
   platform, product-tour and the application-metadata glossary entry, in
   en and zh-Hans. The tiles now carry README facts: AGENTS.md written by
   the scaffolder, the four gates, "apps small enough for AI to hold whole",
   one definition, one compiled artifact, Apache-2.0.
2. Old descriptors replaced with positioning[locale]: product-tour's
   "production platform", platform's "target format and runtime", the home
   kicker (now T1 in all seven authored locales), the glossary heading, the
   articles lead and the topic-hub description in src/lib/terms.ts.
3. "Works on your existing systems" carries the README limit everywhere it
   appears: federation is read-only by default and early; writes are enabled
   deliberately per object. Home (seven locales), product-tour, data-modeling,
   the four cluster texts in src/lib/clusters.ts and the six per-locale
   cluster templates.
4. The README agent path (npm create objectstack@latest, point a coding agent
   at the generated project and its AGENTS.md, npx os dev --ui) replaces the
   invented rule file on agent-developer and is linked from platform, mcp,
   ai and home; the docs-root links become deep links.
5. Capability pages stop repeating each other: six titles drop the "ObjectOS"
   prefix, each lead names the open runtime and what ObjectOS adds, the
   templated "ships in the open-source console" FAQ becomes "What is open
   source here, and what does ObjectOS add?" with a page-specific answer,
   and the hero CTAs vary (docs deep links, the agent path) instead of nine
   copies of "Compare editions and pricing". The self-hosted-ai cluster now
   separates the free ObjectStack framework from licensed self-managed
   ObjectOS, in en and zh-Hans, and names the product path.
6. The FAQ heading reads "Frequently asked questions"; the five security
   section kickers no longer repeat their headings; the home AI link says
   where it goes; $100k on the English automation page; the fifth reference
   story gets its table row; "25+" and "16+" replaced with sourced facts
   (12 README primitives; Console renders, Studio authors); "Platform-native
   assistants only" dropped; the security blog CTA is one article in every
   locale.
7. Plan details: platform's self-hosting FAQ names Business Self-Managed;
   the trust-center identity row puts SSO / SAML on Business (pricing page)
   and keeps SCIM on Enterprise (licence page); the Enterprise contact is
   sales@ (licence page); llms.txt's pricing line lists every delivery.

Prices, the annual-billing wording, the signup and Start targets, layout,
CSS, astro.config.mjs and scripts/lib are untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Carried here from the #209 post sweep. ai-ontology-open-protocol: "ObjectOS
adds the commercial production experience" becomes the canonical descriptor
("ObjectOS, the commercial runtime environment built on ObjectStack, adds…")
in all seven authored locales. self-hosted-ai-app-platform: the section
heading no longer calls ObjectOS "the optional operations layer"; it names
ObjectOS as built on the runtime. extend-existing-systems-with-ai: the
description and the opening claim state the E1 assumption — a self-managed
ObjectOS deployment (Enterprise, on your own infrastructure) where the
database is reachable — and the read-only default. zh-Hant regenerated.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… the record-approvals inbox

The approvals page, the approval-workflow glossary entry and llms.txt said
AI-proposed structural changes ride "the same queue" / "the same approvals
inbox" as record approvals. docs.objectos.ai build/ai-builder (objectos
origin/main 5d2f837, "Human-in-the-Loop approval") describes something else:
tool calls that mutate metadata route through a pending-action queue
(/api/v1/ai/pending-actions), approvers see the proposed diff, ai:approve is
held by Setup Administrator by default and can be scoped per package, and
every approve or reject lands in the audit log with the conversation id. It
is an ObjectOS (in-app AI Builder) feature; the record-approvals inbox is the
open-source console's.

The page now says both: the inbox is what it is, and the AI Builder's changes
wait in their own pending-action queue (diff, ai:approve, audit). The section
"People and AI are both reviewed before anything ships" keeps the idea without
claiming one shared queue. en and zh-Hans; zh-Hant derived at build. Grep of
every non-blog locale source for the same claim reads zero; no blog post
carries it.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
#222 added five section kickers to the security page and left faqKicker as
'FAQ' in the five translated locales. #219's helper marks any field whose
locale value equals the English value as lang="en", so those pages rendered
one English eyebrow each — harmless now, but it would keep the page counted
as partly English, and noindexed, once the rest of the locale is translated.

The kicker now follows the site's own per-locale FAQ wording: es keeps the
cluster label 'Preguntas frecuentes'; ja, de, fr and ko get 'よくある質問',
'Häufige Fragen', 'Questions fréquentes', '자주 묻는 질문'. The other four
kickers were already translated in every locale.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ge and home

At 1366x625 the primary CTA sat below the fold on 14 of 15 marketing
pages (bottom at 626-899px) and at 1207px on home.

Marketing template: the h1 takes a 7.4vh term (46px at 1366x625, 55px
at 1440x750, unchanged from 962px of height up), the copy column is the
wider of the two (1.15fr against 0.75fr), and on desktop the hero's top
padding, the lead's leading and the CTA row's margin are tightened.

Home: a wide but short screen keeps the source order, so the lead and
the CTA row sit above the video; the reorder that put the video first
now fires on narrow screens only. The desktop lead is 40rem wide at
1.72 leading, so the nine-line ja lead fits too.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ne card

The marketing template's item grid was a fixed three columns: 24 of its
37 grids per locale ended on a short row, 14 of them four-card sections
that left one card with two-thirds of its row empty. The glossary index
left its 22nd term, "Vibe coding", alone.

Both grids now sit on six tracks and take their row sizes from the card
count (src/lib/card-rows.ts): 4 is 2 + 2, 5 is 3 + 2, 22 is six rows of
three and two of two. At two columns an odd last card takes the row.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Measured over every h1 and h2 of the home, platform, pricing, security,
FDE, glossary-index and 15 marketing pages at the six viewports, ja
headings split a word 61 times (ソフトウェ / ア, 環 / 境) and zh-Hans 241
times (可投 / 入生产, 开 / 放, 一整 / 个), with 103 one-character zh last
lines (让人来 / 审).

src/lib/cjk-phrases.ts cuts a zh or ja heading into phrases from the ICU
word segmenter: ja particles, okurigana and auxiliaries stay with their
word, katakana splits are rejoined, zh one-character words join a
neighbour. The heading renders a <wbr> between phrases and sets
word-break: keep-all, the form BudouX emits, so it behaves the same in
every engine; word-break: auto-phrase is Chromium-only and ja-only. The
copy is unchanged: the phrases rejoin to it exactly.

Applied to the h1 and h2 of the marketing template, home (and the home
eyebrow), the security page and the glossary index. Where a Japanese
phrase was wider than its line, the size now fits it: home section
headings at 2.5rem at most, the home h1 at 2.15rem on phones, the
security h1 and h2 on phones.

After: 0 word splits, 0 particle splits and 0 one-character last lines
in ja and zh-Hans at all six viewports.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
"What changes, who reviews it, what runs" left "runs" alone at every
desktop width on all 15 marketing pages; home left "agents." and
"inside" alone; on phones a forced <br> left lines such as "model,".
Measured over the same heading set: 146 such last lines in en, 151 in
ja (its marketing pages are the English copy).

h1 and h2 take text-wrap: balance in the marketing template, home, the
security, pricing, legal and cluster pages and the glossary index. The
two-part headings on home and the security h1 are two block lines, each
balanced on its own, instead of text either side of a <br>. After: 0 in
en, ja and zh-Hans at the six viewports.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
Page titles were set in the display serif on home, the marketing pages,
the glossary and the blog, and in the text sans on pricing, security,
the legal pages and the topic clusters; section headings split the same
way. Card titles were sans everywhere except the glossary index.

Display face (--font-display) for page titles (h1) and section headings
(h2) on every page: pricing, security, legal and cluster now take it.
Text face (--font-sans) for card and item titles: the glossary index's
term cards now take it. Mono stays the label face.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…k mode

In dark mode the note over the home hero image stayed the light theme's
near-white glass box, which stood out the more once the image under it
was dimmed. It now takes the dark surface (--surface at 90%, --line
border) with --ink text.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…and clear of the play button

The poster's words were baked into its SVG: English on zh, ja, de and
every other locale, and at 360px the 1600px card was drawn at about 0.2x,
its sub-line at about 5px. At 390 and 360 the centred play button also
covered the top of the "ObjectStack" title.

The poster image is now the artwork alone; VideoEmbed draws the words
over it as HTML, placed where the artwork drew them and sized to the
frame in container units, with the same face and colours. The title is
the locale's existing video caption ("ObjectStack 90 秒总览", "ObjectStack
を / 90 秒で"); the eyebrow, which names the licence and exists only in
English, stays in every locale; the English sub-line is drawn on the
English page only. Below 560px of frame the sub-line is left out, the
eyebrow keeps a floor and wraps only between its parts, and the play
button moves up and shrinks to sit between the eyebrow and the title.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…s and cluster pages

Measured on the previous tip, 157 of 450 zh/ja h1/h2 outside the
marketing templates still split a word across lines: 125 blog list card
titles, 26 on blog posts and 6 on cluster pages (企 / 业, アプリケー /
ション).

The phrase module moves to scripts/lib/cjk-phrases.mjs so two consumers
share it, as cjk-segment-break.mjs is shared:

- phraseHtml now covers the blog card title (ArticleCard), the post
  title and related-posts heading (BlogPost) and the cluster h1, h2 and
  card titles (ClusterPage), each with keep-all on zh and ja headings.
  Its escaping is now Astro's own (html-escaper), so a heading with no
  wbr renders byte for byte as {text} did.
- rehypeCjkHeadingPhrases, added to markdown.rehypePlugins after the
  segment-break step, gives the h1-h6 a zh or ja post body authors the
  same wbr, and BlogPost sets keep-all on them. It reads the locale from
  the source file name, runs only inside headings, never enters code,
  pre, kbd or samp, and adds a wbr only beside text.

A ja one-kanji suffix after a stem with okurigana now stays with it
(働き方), and the ja cluster h1 is sized to hold an eleven-character
katakana word (アプリプラットフォーム).

Byte diff against the previous tip: every en, de, es, fr and ko page,
and every root file, is unchanged apart from hashed asset names; 198
zh/ja pages differ only by wbr, all 6713 of them inside h1-h6 and none
inside code. After: 2 of 450 headings split, both one ja card title
whose 15-character katakana word is wider than a card line.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…26-10-15

The linear stack (lin/issue-216 through lin/issue-223, base 53764e0) was
staged on 2026-10-06/07, and the #209 post-side sweep stamped every
locale file it revised with the staging day: 111 frontmatter lines across
18 posts read `updated: 2026-10-06T10:00:00+08:00` (32 of them replaced
an earlier 2026-09-02 value, 79 were new).

The stack lands on main on Thursday 2026-10-15, and merge time is publish
time on this site. The standing ruling is that `updated` is the day the
revision goes live: it records when readers got the substantive revision,
and it becomes dateModified in the BlogPosting JSON-LD,
article:modified_time and the RSS item's atom:updated. A staging date
nine days before anyone could read the change misstates all of those.

So every `updated:` value the stack introduced that is earlier than
2026-10-15 becomes `2026-10-15T10:00:00+08:00`. The authored locales
(en, de, es, fr, ja, ko, zh-Hans) are edited; zh-Hant is regenerated with
`pnpm gen:zh-hant`. No `date:` line moves, and no `updated:` value the
stack did not introduce is touched; the diff against lin/issue-223 is
111 `updated:` lines and nothing else.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… locale

The poster's eyebrow ("OPEN PROTOCOL & RUNTIME · APACHE-2.0") was English on
all eight locales and the sub-line ("Data model · UI · workflows · permissions
— one context window.") was drawn on en only. Both now live in homeCopy as
videoPosterEyebrow and videoPosterNote for zh-Hans, en, ja, de, es, fr and ko,
with zh-Hant derived by the same s2t path as the rest of the home copy, and
MarketingHome passes them as poster text in every locale.

Sources: the eyebrow quotes the objectstack README's licence sentence ("the
open stack — protocol … and the production runtime, Apache-2.0"), and the
sub-line's tail now quotes the README's size line, "Apps small enough for AI
to hold whole.", in the wording each locale's aiCopy already gives it. "One
context window" does not survive ruling Q5 on #209: it was the title of the
proof chip that carried the 150k-token figure, both retired together by the
PR that executed Q5, and the README at origin/main no longer says it. Nouns
follow each locale's vocabulary in aiCopy and positioning.ts.

Below 400px of frame the longer de/es/fr eyebrows wrap to two lines (between
their parts, as designed) and the play badge's top edge met the second line's
box at 360; the badge moves from 40% to 44% in that range so it keeps about
6px above a two-line eyebrow and about 18px above the title.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… is its single-node entry (objectos#295 ruling A)

Follows the maintainer's three answers on objectstack-ai/objectos#295
(2026-10-12): self-managed ObjectOS is ObjectOS Enterprise (A1), Enterprise
is self-managed only and Cloud tops out at Business (A2), and the single-node
SKU keeps the name Business Self-Managed and its price but is described as
the single-node entry option of ObjectOS Enterprise (A3).

- billing note (en, zh-Hans): no longer says self-managed "starts on
  Business"; states that Cloud runs up to Business, self-managed is
  Enterprise, and Business Self-Managed is Enterprise's single-node entry.
  The annual-billing sentence (#214) is untouched.
- Business card: the "Self-Managed option" bullet leaves the Cloud card.
- Enterprise card: the deployment bullet now reads "Self-managed on your own
  infrastructure, including air-gapped" instead of "Full private deployment",
  and gains the single-node entry bullet, placed right after it so the two
  self-managed facts read together and "Volume AI-seat pricing" follows as
  the next tier.
- $540 / AI seat / yr and 10-25 AI seats are unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
… pages, clusters and llms.txt

Every string that listed Business Self-Managed and Enterprise as two
sibling self-managed editions, or gave Business a self-managed delivery,
now reads the ruling's way: self-managed ObjectOS is ObjectOS Enterprise,
Cloud tops out at Business, and Business Self-Managed is Enterprise's
single-node entry option (name and price unchanged).

- content/pages/{en,zh-Hans}/ai.ts: table rows "ObjectOS Business (Cloud)"
  and "ObjectOS Enterprise (self-managed)"; the Enterprise cell says "on your
  own infrastructure" instead of "private deployment"; the models FAQ.
- content/pages/{en,zh-Hans}/platform.ts: the open-source FAQ and the
  "own infrastructure" FAQ (still three ways).
- content/pages/{en,zh-Hans}/data-modeling.ts: the "what does ObjectOS add"
  FAQ.
- content/pages/{en,zh-Hans}/forward-deployed-engineers.ts: lead and the
  "Do I need ObjectOS" FAQ.
- content/pages/{en,zh-Hans}/trust-center.ts: the boundaries copy; en
  "ObjectOS Self-Managed" (an edition name) becomes "Self-managed ObjectOS
  Enterprise".
- src/lib/clusters.ts, src/lib/cluster-i18n.ts: the self-hosted cluster's
  definition and FAQ.
- src/pages/llms.txt.ts: the Pricing line.

Lower-case "self-managed" describing a deployment, blog posts, legal
pages and positioning.ts E1 are untouched.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…so the name does not break at its hyphen

Measured at 1366-1920 wide: with "(Business Self-Managed)" in the card
bullet, CJK line-breaking split the name at its hyphen ("(Business Self-" /
"Managed),") and the bullet ran five lines. The card bullet now names the
SKU as 商业版自管; the billing note directly beneath still carries the full
name, 商业版自管(Business Self-Managed), as it did before this card.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…ode to air-gapped clusters (objectos#295 answers 2 and 3)

"For larger organizations with custom security, deployment, and
contracting needs." sat above a card whose first two bullets now say
self-managed only and a 10-25-seat single-node entry. The tagline now says
what the edition is: ObjectOS run on your own infrastructure, from one
node up to air-gapped clusters.

Length was measured, not guessed. The tagline is a shared subgrid track, so
the longest tagline sets the price row for all four cards. The seat's
proposed en copy ("For organizations that run ObjectOS on their own
infrastructure, from a single node to air-gapped clusters.") wraps to four
lines at 1366-1920 (the grid is capped at 1120px, so every desktop width
measures the same) and moves the price row from 525px to 547px in all four
cards; so does every variant that keeps "organizations" with "own
infrastructure" and "air-gapped clusters". The chosen en copy keeps those
two phrases verbatim, stays at three lines like the tagline it replaces,
and leaves the price row at 525px. The seat's zh-Hans copy goes from two
lines to three; the chosen zh-Hans copy keeps 组织, 自管 and the
single-node-to-air-gap range at two lines, price row unchanged at 475px.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 15, 2026 13:47
@hotlong
hotlong merged commit 6ada491 into main Oct 15, 2026
1 check passed
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.

Pricing copy: Enterprise is self-managed only, and Business Self-Managed is Enterprise's single-node entry (objectos#295 ruling A)

2 participants