Repository navigation
Commit 3da9a4c
feat(spec,formula,lint): a mail template's
<!-- claude-projects-attribution: {"github_login":"os-sales"} -->
_Requested by **Sales** · [project
thread](https://claude.ai/code/project/chan_01D5bWFY3rFzgJcytD5gD4Uu?thread=cmsg_01D5bWFY3rFzgJcytD5gD4Uu8VsRMt9NAU2MFrY4eYtfoM)_
domain:spec#2 · os-sales · session_01L6bhgUtH2CXA8Qb8bDBRom
Fixes #30172
Clause-②: yes (widening)
Metadata-protocol: added
Skills-docs: follow-up #30731 — `skills/objectstack-automation/SKILL.md`
(the `notify` section) gains one line: an option label in a notice is
written with the mail formatter `{{ stage | option:opportunity.stage
}}`, not with `label()` in `templateData`. The skills lane owns that
file.
## 维护者速读
- 改了什么:邮件模板新增 `option` 格式化器,写法 `{{ stage | option:opportunity.stage
}}`,参数是不带引号的「对象.字段」;spec 的模板文法句、formula 的邮件文法与格式化器、lint
的邮件模板规则同步认它;对象按引用阶梯解析(应用自有对象、制品 `packages[]`
里其他包提供的对象、平台对象;带平台前缀却无人注册的名字在 CLI 只给 warning,与
`object-reference-unregistered-platform` 同级;发布门只认 live
universe,不在其中的名字一律报错;写入某个包时只认该包的依赖闭包,闭包外的对象报「在闭包外」并提示声明依赖);站内模板文字(流程通知标题/正文、操作确认语)照旧当作未知格式化器拒绝。
- 为什么改:流程通知只填一次就发给不同语言的收件人,选项字段(如商机阶段)只能印出存储值 `needs_analysis`;裁决 F
定下用邮件模板格式化器按收件人语言印标签,本 PR 是其中 spec 一半。
-
风险与回滚:只放宽,以前通过的模板全部照旧;保存时对象不存在、字段不存在、字段没有选项、参数写法不对都会报错。渲染时没有拿到该「对象.字段」的标签表就抛出
`optionTableRefusal` 的句子,不发送,绝不拿存储值冒充标签;所以 server 一半(plugin-email
按收件人语言建标签表)落地前,这种洞能通过校验但发送会失败,席位因此让本 PR 与 server 一半背靠背落地。回滚 = revert 本
PR。
- 先看:`packages/formula/src/template-engine.ts` 的 `option`
格式化器、`optionTableRefusal` 与 `judgeHoleFormatter`
新增的两段;`packages/formula/src/mail-hole-grammar.ts` 的
`optionFieldRefusal`;`packages/lint/src/validate-email-template-hole-grammar.ts`
的 `optionFieldIndex`。
- 两分钟验证:运行 `pnpm --filter @objectstack/formula exec vitest run
src/template-option-formatter.test.ts`,预期 51 例全绿,其中「the formatter, given
the label table」一组按 en / zh-CN 两张表分别印出 `Needs Analysis` 与
`需求分析`,没有表时抛出拒绝句。
- 需要你做的:无,席位与 server 一半背靠背落地。
## What changes
| Package | Change |
| --- | --- |
| `@objectstack/spec` | `MAIL_TEMPLATE_HOLES` (the `subject` /
`bodyHtml` / `bodyText` descriptions of `email_template`) gains one
sentence, `OPTION_ARGUMENT_RULE` word for word, including the
multi-select reading. The reference page is regenerated. |
| `@objectstack/formula` | `FORMATTERS` gains `option`;
`MAIL_TEMPLATE_FORMATTERS` (new) is the mail list, `TEMPLATE_FORMATTERS`
stays the in-app list. `judgeHoleFormatter` judges the known set per
dialect: in-app, `option` is `known-formatter` with a sentence saying it
formats only in a mail template; in mail, an argument that is not a bare
`object.field` is `option-field-shape`. `parseMailHole` returns
`optionField: { object, field }`. `judgeMailHoleGrammar(face, { objects
})` judges the reference against the objects the caller hands in
(`option-object-unknown`, `option-field-unknown`,
`option-field-no-options`; an object handed in as `null` resolves with
its field not judged; a name it does not carry that has a platform
prefix, `sys_` / `cloud_` / `ai_`, is rung ④ of the object-reference
ladder, neither refused nor judged; with `liveUniverse: true`, objects
that are the deployment's whole served universe, there is no rung ④ and
such a name is refused; with `packageScope: { packageId }`, objects
narrowed to a written package's closure, a name they do not carry is
`option-object-outside-closure`, naming the declare-the-dependency
remedy). `formatValue` takes `optionLabels` (`OptionLabelTable`, new).
New exports: `OPTION_ARGUMENT_RULE`, `optionFieldReference`,
`optionTableRefusal`, `optionObjectUnregisteredPlatform`,
`MAIL_TEMPLATE_FORMATTERS`, types `OptionLabelTable`,
`OptionFieldReference`. |
| `@objectstack/lint` | `email-template-hole-grammar` gains an arm (no
new rule id, since this rule already owns the hole). It resolves the
object on the object-reference ladder: the stack's own objects as the
registry serves them (`objectsAsServed`, so `objectExtensions` fields
count; the injected columns beside the declared fields; options read by
`declaresOptionsOnceServed`, so a `picklist` name counts), then the
objects another entry of the artifact's `packages[]` provides, then
`PLATFORM_PROVIDED_OBJECT_NAMES`, whose field is left to the publish
door and the send. A platform-prefixed name none of these carries is
rung ④: advised at `warning` under the same rule id, field unjudged, as
`object-reference-unregistered-platform` advises
(`EmailTemplateHoleGrammarFinding.severity` widens to `'error' \|
'warning'`). At the publish door (a set `runtimeWriteType`, passed as
`validateEmailTemplateHoleGrammar(stack, { liveUniverse })`) the index
is the live universe alone: no `packages[]` rung, no platform-name rung,
no rung ④, so a name it does not carry is refused, the reading
`list-view-object-unknown` gives. A write into a package
(`ctx.packageScope`, passed beside `liveUniverse`) is judged against
that universe narrowed to the package's closure: an object outside it
gets the closure verdict and `closureDependencyRemedy`'s hint, the
reading `list-view-object-outside-closure` gives.
`EmailTemplateHoleGrammarOptions` is exported beside
`EmailTemplateHoleGrammarFinding`. The finding's hint quotes
`OPTION_ARGUMENT_RULE` for an `option-*` refusal. |
| `content/docs/automation/email-templates.mdx` | One paragraph for
`option` (the ladder, multi-select, the refusal with no table); the
known-formatter paragraph names `MAIL_TEMPLATE_FORMATTERS`. |
The formatter: with a table for the hole's `object.field`, a value
prints as its label and a value outside the options prints as stored
(ruling parameter 3); a multi-select value labels each element, joined
with `, `; a missing value prints empty. With no table for that
`object.field` (no table at all, or a table for another field), the call
throws a `RangeError` carrying `optionTableRefusal`'s sentence, the
`currencyDefaultRefusal` shape, so a renderer can ask it before it
formats: the stored value is never printed where a label was promised.
This package reads no metadata and no translations. Building the table
per recipient locale, and refusing at send an object, field or option
list that does not exist (`TEMPLATE_INVALID`), is the server half
(`plugin-email`).
### FROM → TO at each door
Measured on `origin/main` `4bf9a2d7` (a base worktree, built) and on
this branch, the same probe on both trees
(`runAuthoringRules('validate', …)` on the parsed stack,
`runRuntimeAuthoringRules` for an `email_template` write with the live
objects, `judgeMailHoleGrammar`, `templateEngine.compile`, and
plugin-email's `EmailService.renderTemplate` / `sendTemplate`). The
stack declares `opportunity` with `stage` (select, options) and `name`
(text). The ladder rows are pinned in
`validate-email-template-hole-grammar.option.test.ts` at `d73ae610`.
| Door | Hole | `main` | this head |
| --- | --- | --- | --- |
| `os validate` | `{{ stage \| option:opportunity.stage }}` | `error`
`email-template-hole-grammar`: names the formatter 'option', which is
not a template formatter … Known formatters: upper, lower, trim, number,
currency, percent, date, datetime, truncate, default, json | nothing |
| publish door (`email_template` write) | same | the same refusal |
nothing |
| renderer grammar (`judgeMailHoleGrammar`) | same | `known-formatter` |
nothing |
| `os validate` and publish door | `{{ name \| option:opportunity.name
}}` | `known-formatter` refusal | `error`: names the field
`opportunity.name`, which declares no options … |
| `os validate` and publish door | `{{ stage \| option:deal.stage }}` |
`known-formatter` refusal | `error`: names the object `deal`, which
resolves to no object this app defines, a package of its artifact
provides or the platform provides … |
| `os validate`, the object only in a sibling `packages[]` entry | `{{
stage \| option:opportunity.stage }}` / `{{ name \|
option:opportunity.name }}` | `known-formatter` refusal | nothing /
`error`: names the field `opportunity.name`, which declares no options
(the sibling's fields are judged) |
| `os validate` / `os build`, a platform object the stack does not carry
| `{{ status \| option:sys_user.status }}` | `known-formatter` refusal |
nothing (field left to the publish door and the send) |
| publish door, live universe without `sys_user` | same |
`known-formatter` refusal | `error`: names the object `sys_user`, which
resolves to no object …; accepted once the live universe serves
`sys_user` with a select `status` |
| `os validate` / `os build`, a platform-prefixed name no known package
registers | `{{ status \| option:sys_userz.status }}` |
`known-formatter` refusal | `warning`: names the object `sys_userz`,
which has a platform prefix, but no known package registers it … (rung
④; field not judged); control `userz.status` refused at `error` |
| publish door, same | same | `known-formatter` refusal | `error`: names
the object `sys_userz`, which resolves to no object … |
| publish door, a write into package `com.acme.crm` (dependency
`com.acme.core`), `opportunity` served by an undeclared package | `{{
stage \| option:opportunity.stage }}` | `known-formatter` refusal |
`error`: names the object `opportunity`, which is outside package
`com.acme.crm`'s closure …; hint: Declare a dependency on the package
that defines "opportunity" in the `dependencies` of "com.acme.crm"'s
manifest … ; an object of `com.acme.core` accepted; the same write
naming no package accepted |
| `os validate` and publish door | `{{ stage \|
option:'opportunity.stage' }}` | `known-formatter` refusal | `error`:
gives the option formatter the argument `'opportunity.stage'` … one
object name and one field name joined by a dot, written bare |
| in-app `templateEngine.compile` (notice title / message, confirmText)
| `{{ record.stage \| option:opportunity.stage }}` | refused: invalid
template hole … holes are a field path with an optional formatter …
Known formatters: (the eleven) | refused: names the formatter 'option',
which is not a template formatter … `option` formats only in a mail
template … Known formatters: (the same eleven) |
| `os validate`, any mail hole with a misspelled formatter | `{{ amount
\| curency:EUR }}` | Known formatters: … json | Known formatters: …
json, option |
### The window between the halves
Until the server half lands, `plugin-email` hands `formatValue` no label
table. Measured on `d73ae610` (formula rebuilt) through
`EmailService.renderTemplate` and `sendTemplate`, a template in `en-US`
and one in `zh-CN`, every face holding `{{ stage |
option:opportunity.stage }}`, with a transport that records each message
it is handed:
| Recipient locale | `stage` | `main` | this head (no table) |
| --- | --- | --- | --- |
| en-US, zh-CN | `needs_analysis` | `TEMPLATE_INVALID` … names the
formatter 'option' …; nothing sent | render and send throw: the hole
option:opportunity.stage prints the option labels of
`opportunity.stage`, and no label table for that field reached this
render … The stored value is not printed in place of the label. (then
`OPTION_ARGUMENT_RULE`); nothing sent |
| en-US, zh-CN | `lost_to_rival` (not an option) | `TEMPLATE_INVALID`;
nothing sent | the same refusal; nothing sent |
With the table the server half will pass (`formatValue('option',
'needs_analysis', 'opportunity.stage', { optionLabels })`), the en table
prints `Needs Analysis` and the zh-CN table `需求分析`; `lost_to_rival`
prints as stored (pinned in `template-option-formatter.test.ts`).
So in the window a template that validates fails at send, loudly, and
never delivers the stored value in place of a label. The seat holds this
PR's landing so it lands back to back with the server half, and no
release ships the window.
## Tests
- `packages/formula/src/template-option-formatter.test.ts` (new, 51):
the two lists; the argument parse (2 accepted, 12 refused shapes); the
mail dialect accepts, refuses six malformed arguments naming the hole,
the argument and the rule; the object / field / no-options judgement
with its no-objects control and an object handed in as `null` (field not
judged); a `sys_` / `cloud_` / `ai_` name the objects do not carry
neither refused nor judged, with the unprefixed `userz` control refused;
with `liveUniverse: true` the same three names refused; objects narrowed
to a package closure refusing `deal` and `sys_userz` as outside it,
naming the remedy, and accepting a carried field; the in-app judge, and
compile + `validateExpression` + evaluate refusing it in a notice title,
a notice message and a confirm sentence; the formatter with an en and a
zh-CN table, outside the options, multi-select and a missing value; with
no table and with a table for another field it throws the refusal;
`optionTableRefusal` answers null for a table that holds the field and
the sentence otherwise; the spec faces quote `OPTION_ARGUMENT_RULE`,
multi-select clause included.
-
`packages/lint/src/validate-email-template-hole-grammar.option.test.ts`
(new, 29): a select field accepted in every face; a `picklist`-bound
field and an `objectExtensions` field accepted; an object a sibling
`packages[]` entry provides, its fields judged; a platform object the
stack does not carry (`sys_user.status`) accepted; `sys_userz.status`
advised at `warning` with the unprefixed `userz` control refused at
`error`; side by side, `os validate` and `os build` answering from the
ladder (`sys_userz` a warning, `sys_user` nothing) while the publish
door refuses both at `error` against a live universe without them, and
accepts `sys_user` once the live universe serves it; a write into a
package: an object of an undeclared package gets the closure verdict and
the declare-the-dependency hint, an object of a declared dependency is
accepted, and the same write naming no package is accepted; the control
(the same hole refused once the object is not declared); six refusals
naming the hole and what to fix; each face on its own path; `os
validate` / `build` / `lint` on the parsed stack; the publish door with
and without the object in its live universe; a flow notice `title` and
`message` at `os validate`, and an action `confirmText`, refused.
- Moved pin:
`validate-email-template-hole-grammar.unknown-formatter.test.ts` asserts
the mail refusal lists `MAIL_TEMPLATE_FORMATTERS` (it listed
`TEMPLATE_FORMATTERS`, which no longer ends the mail list).
- Flipped pin (patch round): the no-table case and the
table-for-another-field case printed the stored value; they now throw
the refusal.
## Verification
<details><summary>Suites and typecheck</summary>
Head 98f61ec (package-closure verdict, options type export; touches
formula and lint only), formula rebuilt:
- `pnpm --filter @objectstack/formula test` exit 0: Test Files 65
passed, Tests 1703 passed
- `pnpm --filter @objectstack/formula typecheck` exit 0
- `pnpm --filter @objectstack/lint test` exit 0: Test Files 196 passed,
Tests 8203 passed
- `pnpm --filter @objectstack/lint typecheck` exit 0
- eslint `--no-inline-config` on the nine changed `.ts` files: exit 0.
- The four changeset gates: each exit 0.
Head 7cea418 (the publish door reads the live universe alone; touches
formula and lint only), formula rebuilt:
- `pnpm --filter @objectstack/formula test` exit 0: Test Files 65
passed, Tests 1702 passed
- `pnpm --filter @objectstack/formula typecheck` exit 0
- `pnpm --filter @objectstack/lint test` exit 0: Test Files 196 passed,
Tests 8200 passed
- `pnpm --filter @objectstack/lint typecheck` exit 0
- eslint `--no-inline-config` on the nine changed `.ts` files: exit 0.
- `check-changeset-no-major --base origin/main`, `check-empty-changeset
--base origin/main`, `pnpm check:changeset-gate-self-tests`,
`check-changeset-fixed`: each exit 0.
Head 030a493 (rung ④): formula 1701, lint 8198, both typechecks, eslint
and the four changeset gates exit 0.
Head d73ae61, after a full turbo build:
- `pnpm --filter @objectstack/formula test` exit 0: Test Files 65
passed, Tests 1700 passed
- `pnpm --filter @objectstack/lint test` exit 0: Test Files 196 passed,
Tests 8196 passed
- formula, lint and spec typecheck exit 0
- `pnpm --filter @objectstack/spec test` exit 0: Test Files 817 passed,
Tests 25792 passed, 1 todo
- eslint `--no-inline-config` on the nine changed `.ts` files: exit 0.
- Round 1 (head `29f86544`): `spec check:generated` reported
`content/docs/references/**` stale; `--fix` regenerated it and
re-checked green. The patch round's regenerated page is covered by the
derived `check:generated` family below.
</details>
<details><summary>Ablation (through scripts/ablation-replace.mjs; each
restored blob equals HEAD and git diff HEAD is empty)</summary>
Head 98f61ec:
- The closure branch: `...(ctx.packageScope ? { packageScope:
ctx.packageScope } : {})` in the rule's registry entry gated on a
`__ablated_closure` marker (anchor 1 → 0, blob eaef25c → 9662b429).
`validate-email-template-hole-grammar.option.test.ts`: 1 failed, 28
passed (29), the outside-closure pin. Restored blob eaef25c == HEAD,
`git diff HEAD` empty, tree clean, 29/29.
Head 7cea418:
- The publish door's switch: `liveUniverse: ctx.runtimeWriteType !==
undefined` in the rule's registry entry gated on a `__ablated_live`
marker, so the door walks the CLI ladder (anchor 1 → 0, blob 3cb1d3c →
baa96acd). `validate-email-template-hole-grammar.option.test.ts`: 2
failed, 24 passed (26), the two side-by-side door pins (`sys_userz`,
`sys_user`). Restored blob 3cb1d3c == HEAD, `git diff HEAD` empty, tree
clean, 26/26.
Head 030a493:
- Rung ④: `optionObjectUnregisteredPlatform` made to answer false
(anchor 1 → 0, blob 9d18678 → 25b7acc6), formula rebuilt,
`ablation-dist-preflight` marker present in 2 built files.
`template-option-formatter.test.ts`: 1 failed, 48 passed (49), the rung
④ pin; `validate-email-template-hole-grammar.option.test.ts`: 2 failed,
22 passed (24), the `sys_userz` warning pin and the validate / publish
agreement pin. Restored blob 9d18678 == HEAD, formula rebuilt,
preflight `--absent` exit 0, tree clean, 49/49 and 24/24.
Head d73ae61:
- Must-fix: the `optionTableRefusal(...)` call in the formatter replaced
by a constant `null` (anchor 1 → 0, blob 8b10538 → 465302d3).
`template-option-formatter.test.ts`: 2 failed, 46 passed (48); the two
red are the no-table and other-field pins.
- Ladder, platform rung: the `PLATFORM_PROVIDED_OBJECT_NAMES` loop given
an empty list (blob f52aa4a → 052030ea).
`validate-email-template-hole-grammar.option.test.ts`: 1 failed, 21
passed (22); the red is the `sys_user.status` pin.
- Ladder, packages rung: the `packagesOf(stack)` loop given an empty
list (blob f52aa4a → d7c112de). Same file: 1 failed, 21 passed (22);
the red is the sibling-package pin.
- Restored: both files green again (48/48, 22/22 at that head), tree
clean.
- Round 1 (head `29f86544`): the `option:` key of `FORMATTERS` renamed
`__ablated_option`; formula 21 failed of 40, lint 17 failed of 20;
restored green.
</details>
<details><summary>Derived gates (head d73ae61, after a full turbo
build; 56928a5, 030a493, 7cea418 and 98f61ec re-ran the changeset
gates above)</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`: 102 families on `d73ae610`; each run with
its exit code captured before any pipe: **102 exit 0**.
- `dispatch-gates --ran`: 102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUN.
- `check-changeset-no-major`: exit 0, no `major` bump (three `minor`
changesets: spec, formula, lint).
- `check-dev-prereqs --self-test`: exit 0.
- Not run locally, CI-owned: the repo-wide `pnpm lint` scan,
`test:repo`, and the families outside the derived total.
- `origin/main` has moved 21 commits past this branch's merge base (head
`05a30e6e`); `git merge-tree` against it is clean. Not merged in this
round, since the landing waits for the server half.
</details>
## Acceptance notes
- **This PR is the spec half of ruling F (6670405874) and closes the
card.** The server half (`plugin-email`: the label table per
`object.field` built in the recipient's locale through the
`readerOptionLabels` path, the per-recipient render, `TEMPLATE_INVALID`
at send for a missing object / field / option list, and its en / zh-CN,
scheduled-flow and non-option pins) is its own card, cut by the seat.
- **The window is closed by the landing order, and fails loudly if it
opens.** The seat lands this PR back to back with the server half.
Should a build carry this half alone, an `option` hole validates and its
send throws `optionTableRefusal`'s sentence: no recipient gets the
stored value where a label was promised.
- **Lint arm, not a new rule id.** The existing
`email-template-hole-grammar` owns the hole, so the object / field /
options check is an arm of it; `rule-explanations.ts` is unchanged.
- **Spelling** follows the seat's decision of record: `option` with a
bare `object.field`, no quotes, no `@path`.
- **Where the app's objects come from.** The CLI doors walk the
object-reference ladder: the stack's own objects as served, then the
objects a sibling `packages[]` entry provides, then the
platform-provided names, whose fields the CLI does not hold, so a field
on one is judged at the publish door and the send. A platform-prefixed
name none of these carries is rung ④, a warning, as the object-reference
rule answers it; an unprefixed one is refused. The publish door has no
ladder: it judges against the live universe alone and refuses a name it
does not carry, whatever its prefix, so a template naming an object the
deployment does not serve is refused at save, not stored to fail at
send. A write into a package is judged against that package's closure,
and an object outside it is refused with the closure verdict and the
declare-the-dependency remedy. The publish door reads the live universe
the gate carries.
- **The in-app refusal sentence moved.** An in-app hole naming `option`
used to get the generic "holes are a field path with an optional
formatter" refusal (the engine did not know the name at all); it now
gets the `known-formatter` sentence, which says `option` is a mail-only
formatter. Both are compile refusals; no in-app hole that compiled
before stops compiling.
- `label()`'s contract does not move (parameter 7).
- Branch: the card thread's harness-pinned
`claude/project-thread-d8v0qv`, not `claude/issue-30172-*`.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01L6bhgUtH2CXA8Qb8bDBRom)_
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L6bhgUtH2CXA8Qb8bDBRom
<!-- signed: domain:spec#2 · os-sales · session_01L6bhgVugtCCwBs98FeR27P
-->
---------
Co-authored-by: Claude <noreply@anthropic.com>option formatter prints a select value as its option label in the recipient's language (#30727)1 parent d8ed2e1 commit 3da9a4c
15 files changed
Lines changed: 1169 additions & 38 deletions
File tree
- .changeset
- content/docs
- automation
- references/system
- packages
- formula/src
- lint/src
- spec/src/system
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
96 | 96 | | |
97 | 97 | | |
98 | 98 | | |
99 | | - | |
| 99 | + | |
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
| 187 | + | |
153 | 188 | | |
154 | 189 | | |
155 | 190 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
52 | | - | |
53 | | - | |
54 | | - | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
55 | 55 | | |
56 | 56 | | |
57 | 57 | | |
| |||
0 commit comments