Repository navigation
Commit 75237a9
fix(spec)!: the dateRange array arm is exactly two string bounds, and each refusal origin gets a true sentence (#18230)
Fixes #17598
Ruling A (comment 5642584462, decision batch #117 item 3, 2026-09-12;
re-affirmed 5651068700 on 2026-09-13). Both items of the card land here
— see "Why item 2 lands here" below.
## 1. The array arm refuses anything that is not exactly two string
bounds
`AnalyticsDateRangeSchema`'s array arm was `z.array(z.string())` with no
length constraint; it is now `z.tuple([z.string(), z.string()])`. A
tuple rather than `z.array(z.string()).length(2)` on purpose: the arity
is then stated to the author's compiler before any parse runs, which is
the ruling's "the type says what the prose says".
The union's `error` mapper is unchanged in kind, so an arity refusal is
still ONE prescriptive `invalid_union` issue at
`timeDimensions.N.dateRange` and still carries the structural handle
`isAnalyticsDateRangeRefusalIssue` lifts into
`ANALYTICS_DATE_RANGE_UNRECOGNIZED` / 400 — not the tuple arm's own
`too_big` / `too_small` text.
Pinned in
`packages/spec/src/data/analytics-date-range-two-bound-window.test.ts`:
`[]`, `['2026-01-01']`, `['2026-01-01','2026-01-31','2026-02-28']` and
`['{7_days_ago}']` are each refused with one issue at the field's own
path, through `AnalyticsQuerySchema` and through the `POST
/analytics/query` body schema; the two-bound windows and every preset
name still parse; and the authored TYPE narrows too (a
`@ts-expect-error` case).
## 2. Each refusal ORIGIN gets a true sentence, with the origin as a
PARAMETER
`analyticsDateRangeRefusalMessage(input)` becomes
`analyticsDateRangeRefusalMessage(input, origin)`, `origin` being
`'schema'` or `'runtime'`, required, with deliberately no default — a
defaulted origin makes the same false assertion silently for every
caller who does not think about it.
Two clauses were false for an array refused past the schema door:
- "Refused at the schema" — now `'runtime'` answers "Refused past the
schema door, by the analytics reader that received it", and the two
sentences are otherwise byte-identical (pinned by a replace-and-compare
assertion, so the #5240 one-condition-one-wording convention is
mechanical rather than asserted).
- "received an array with a non-string bound" — a new
`describeRefusedDateRange` names the ARITY and the bad bound separately
and claims neither when it is not true: `['2026-01-01']` now reads
"received a 1-element array, not the two bounds [start, end]", while
`['2026-01-01', 3]` keeps exactly the description it had, because that
one was true.
The ruling's acceptance criterion — the wording is true BOTH BEFORE AND
AFTER the arm narrows — is itself a test: `['2026-01-01']` is described
correctly at both origins, and answering item 1 did not falsify item 2's
wording.
## 3. Changeset and ADR-0087 entry
`.changeset/17598-analytics-date-range-two-bound-window.md` declares
**`minor`**, not the `major` the ruling's execution note 1 names, and
says so in its own body. `scripts/check-changeset-no-major.mjs` refuses
a `major` outright: every publishable package is in the Changesets
`fixed` group, so one `major` promotes the whole stack, and during the
launch window this repo ships breaking changes as `minor`. The
information carriers the guard names in its place are both present — the
**BREAKING** banner and the ADR-0087 disposition marker. Measured:
`check-changeset-no-major.mjs --base origin/main` exit 0,
`check-adr-0087-registration.mjs --base origin/main` exit 0.
The ADR-0087 semantic entry is
`packages/spec/src/migrations/entries/semantic/18.analytics-date-range-array-two-bounds-required.ts`.
The `18.` prefix is the TARGET PROTOCOL MAJOR, not a sequence number:
`packages/spec` is at `17.4.0`, the sibling
`18.analytics-time-dimension-date-range-vocabulary-closed.ts` (#16322,
the same schema's string arm) is filed the same way, and the generated
table lands it in `MIGRATIONS_BY_MAJOR[18].semantic` —
`check:migration-registry`, `check:spec-changes` and
`check:upgrade-guide` all green, so the projections agree.
Conversions, per the ruling: one element becomes the same day twice; the
empty array and 3+ bounds get the structured TODO and no conversion,
because there is nothing to derive a pair FROM. No D2 conversion and no
stored-metadata rewrite — rewriting `['2026-01-01']` at load would be
the platform deciding silently that the author meant one day rather than
a window whose end they forgot.
## Scope: the two out-of-declaration edits, measured and then decided
The claim declared `packages/spec/src/data/analytics.zod.ts` plus its
tests, one ADR-0087 entry and a changeset. Two edits reach outside it.
Both were MEASURED before being kept, by ablation with on-disk mutation
proof and a restore proof.
### A. `packages/core/src/utils/analytics-date-range.ts` — REQUIRED
CONSEQUENCE, kept
It is **not** one of the four analytics faces PR #17593 landed. Measured
from that PR's own file list: all 8 files are under
`packages/services/service-analytics/` plus its changeset;
`packages/core` is not among them. This file is the shared ADR-0112
envelope constructor `analyticsDateRangeUnrecognizedError` that those
faces — and driver-memory, and the REST selection door — all call, and
it predates #17593.
It is the only non-test call site of `analyticsDateRangeRefusalMessage`
in the monorepo (grep over `packages/**`: the other five files name only
the constructor, whose signature is unchanged). Making `origin` required
is item 2 of the ruling, so this call site MUST state the origin it
alone knows.
Ablation, one file reverted to the merge base, nothing else:
```
revert packages/core/src/utils/analytics-date-range.ts to 1bdbf82
on-disk proof: two-arg call sites 1 -> 0; blob 50509de -> 4f6d482
pnpm --filter @objectstack/core exec tsc --noEmit :: exit 1
src/utils/analytics-date-range.ts(222,25): error TS2554: Expected 2 arguments, but got 1.
restore (git checkout HEAD -- path; blob back to 50509de, git diff HEAD empty)
pnpm --filter @objectstack/core exec tsc --noEmit :: exit 0
```
The narrowing cannot land without it: `TypeScript Type Check` is one of
the six required contexts.
### B. `packages/spec/scripts/lib/format-type.ts` — REQUIRED
CONSEQUENCE, kept
The reference-docs renderer read only `items`. Draft-2020-12 spells
`z.tuple([...])` as `prefixItems` and leaves `items` ABSENT, so the
array branch rendered the element type of nothing and printed `any[]`.
Measured directly, both renderer versions run over the REAL generated
schema for this field
(`packages/spec/json-schema/data/AnalyticsQuery.json`,
`properties.timeDimensions.items.properties.dateRange`):
```
origin/main arm z.array(z.string()) + base renderer -> string[]
NARROWED arm z.tuple([string, string]) + base renderer -> any[] ** THE REGRESSION **
NARROWED arm z.tuple([string, string]) + this renderer -> [string, string]
```
So landing item 1 WITHOUT this fix does not fail the docs build — it
silently rewrites `content/docs/references/data/analytics.mdx` and
`api/analytics.mdx` from `string[]` to `any[]`, i.e. this card's
narrowing would make the published reference page say the two bounds may
be anything. A regenerated page that is wrong is the criterion, and this
is it. Confirmed the other way too: with the branch reverted,
`check:docs` exits 1 and names all four pages, and
`scripts/format-type.test.ts` goes 1 failed / 89 passed.
Collateral, and unavoidable: the branch is one branch in one renderer
and cannot be scoped to `dateRange` alone, so two pre-existing tuples
stop reading `any[]` in the same regeneration —
`FilterCondition.$between` (`[number | string, number | string]`) and
`ListView.map.center` (`[number, number]`). Both are corrections in the
same direction, on generated pages, with no source change behind them.
They are called out here rather than filed, because there is no PR in
which they could land separately.
### C.
`packages/services/service-analytics/src/__tests__/objectql-daterange.test.ts`
— a THIRD required consequence, found this round
Not in the inherited diff. Narrowing the authored type breaks three
sites in this suite, measured:
```
HEAD (narrowed) pnpm --filter @objectstack/service-analytics exec tsc --noEmit
39 errors total, 3 in objectql-daterange.test.ts (220, 499, 503)
arm reverted + spec rebuilt 36 errors total, 0 in objectql-daterange.test.ts
HEAD + this commit 36 errors total, 0 in objectql-daterange.test.ts
```
(The 36 that remain are 24 TS2307 plus their implicit-any cascade, from
`@objectstack/core` having no `dist` in this worktree. They are
identical in all three runs and CI builds the closure.)
Line 220 is a deliberate one-element window: the value is KEPT and gains
`@ts-expect-error`, because the refusal this test pins is the FACE's,
raised past a door `POST /analytics/dataset/query` never opens —
deleting the case would delete #17593's own coverage. Lines 499/503 are
an ordinary two-bound window whose `const` had no annotation, so
TypeScript widened the literal to `string[]`; it is annotated
`AnalyticsQuery`. Neither touches any face's behaviour. No open PR
touches `packages/services/service-analytics`.
A repo-wide sweep for the same two shapes found no other site: exactly
two `dateRange` array literals in the tree have arity other than 2 (this
one, and this card's own `@ts-expect-error` case), and every remaining
literal sits in a directly contextually-typed position.
## Premise checks
**Two line numbers in the ruling and the card are stale.** The ruling
cites the array arm at `analytics.zod.ts:326` and
`analyticsDateRangeRefusalMessage` at `:284`. On `origin/main`
`1bdbf82cb5` those lines carry unrelated text; the live ones before this
diff were **`:418`** and **`:376`** (they are `:496` and `:425` after
it). Both symbols are unique by name, so the ruling's subject is
unambiguous — only the citations moved.
**Why item 2 lands here: `#17918` does not exist.** The 2026-09-13
re-affirmation routes item 2 to a split card, `#17918`, and permits "one
PR or #17918 first". `GET
/repos//issues/17918` returns **HTTP 404** on
a credential that answers 200 for #17593, #17596, #17124 and #16322 on
the same call shape, so the 404 is that card's own and not a permissions
artefact. Item 2 therefore has no other carrier, and the ruling's own
permission to land both in one PR is what is being used here. This PR
does not name that number in any closing keyword.
**`Clause-②: no` holds against the mechanical check.**
`scripts/pm/check-widening-tells.mjs --declaration no --diff` over this
branch's full diff: **exit 0**, no widening tell. 3 files judged against
a declared surface (the schema source, the migrations registry and the
new entry), 11 NOT MEASURED — the ADR-0087 registration did not fire T4.
The declaration stands as the ruling wrote it and is not flipped.
## Verification
Control, at the tip:
```
pnpm --filter @objectstack/spec exec vitest run --project local
src/data/analytics-date-range-two-bound-window.test.ts
src/data/analytics-date-range-closed-vocabulary.test.ts
scripts/format-type.test.ts :: exit 0 — 3 files, 113 tests passed
pnpm --filter @objectstack/core exec vitest run --project local
src/utils/analytics-date-range.test.ts :: exit 0 — 45 tests passed
pnpm --filter @objectstack/spec typecheck :: exit 0
pnpm --filter @objectstack/core exec tsc --noEmit :: exit 0
pnpm --filter @objectstack/spec check:generated :: exit 0 — all 15 generated artifacts up to date
pnpm lint :: exit 0 (full repo scan, at 5db9a5d)
pnpm check:nul-bytes :: exit 0
```
Negative control for item 1, the whole point of the new suite: with
`analytics.zod.ts` reverted to the merge base (on-disk proof: tuple-arm
occurrences 1 -> 0; blob 69e3ff5 -> 8c2cce9), the two spec suites
go **10 failed / 13 passed** — every `refuses ...` case and every origin
case. Restored from `HEAD`, `git diff HEAD` empty.
Honest reading of the sibling suite:
`analytics-date-range-closed-vocabulary.test.ts` PASSES under that
ablation. Its only edits are the now-required `'schema'` argument
(ignored by the one-parameter function) and one renamed `it` title —
"keeps the array arm exactly as it was" became "still accepts every
two-bound array window", because the old title is false after this
change. **No assertion in it was weakened, deleted, loosened or
skipped**; its three windows all had two bounds already, and the diff is
3 changed lines.
60 gate families run in total, 58 exit 0. Two did not measure anything
and are recorded as such, not as passes: `check:type-check-debt` exits
**3** = `PREREQUISITE NOT MET` (29 workspace dependencies have no built
type entry point in this worktree) and `check:skill-examples` exits 1 on
the same prerequisite (`packages/client-react/dist` holds no
declarations). Both build the closure in CI. The remaining derived
families are repo-wide scans left to CI, per
`scripts/pm/dispatch-gates.mjs --ran` (115 derived, 30 recorded in the
first reconciliation pass, 30 more run after it).
## Acceptance notes
Noted, not filed — each names who would meet it:
- `packages/services/service-analytics/src/date-range-array-arm.ts`
overwrites the shared refusal sentence and documents why: "the shared
wording ... ends with 'Refused at the schema', and neither is true of an
array refused past the schema door by a face". After this PR that
justification is gone — `analyticsDateRangeRefusalMessage(dateRange,
'runtime')` now says something true — so the repo keeps one condition
with two wordings for no remaining reason. That file is fenced to PR
#17593's landing for this round. Successor: whoever next touches that
file, or a follow-up that finishes the #5240 convergence item 2 opened
the door for.
- The same file's header states "`AnalyticsDateRangeSchema`'s array arm
is a bare `z.array(z.string())` with no length constraint" and
"Tightening `AnalyticsDateRangeSchema` itself is `packages/spec`'s call
and is deliberately NOT done here". Both sentences are true of the tree
this PR starts from and false of the tree it produces. Same fence, same
successor.
- `content/docs/references/data/filter.mdx` and `ui/view.mdx` gain
corrected tuple cells with no source change behind them, as set out in
scope decision B. Successor: none needed — they are generated, and the
regeneration is in this PR.
---
## Fix-up round 2 — F1 + F2 + F4
Scope from the at-tier review record (PR comment `5672172206`) and the
seat's rulings
(card comment `5672187836`): **F1 + F2 + F4, nothing else.** Run in
`session_01KB5PFtxuy1x3dcR5gxudx6`. Head `ae337c01a9`. The LEVEL of the
changeset is
untouched — `minor` is ruled.
### Verdict on the rescue commit `1974dd56dc` — right to act, wrong on
the fact; superseded
That commit (seat-made, marked INCOMPLETE AND UNREVIEWED) rewrote the
justification to read
"the string-arm closing on this same schema (#16322) declared
`"@objectstack/spec": patch`".
It was right that the original sentence ("the same grade every other
accept-set narrowing on
this schema has taken") sourced nothing. **Its replacement attribution
does not hold, measured
on this tree:**
- `.changeset/analytics-daterange-driver-alignment.md` (#16322) is still
in `.changeset/`,
unreleased, and describes its own spec entry as **"a
`PROVENANCE_WAIVERS` row only"** — the
driver half of #16041, not an accept-set narrowing at the schema.
- The accept-set narrowing on this schema is **#16041**, and it shipped
`"@objectstack/spec": minor` — `packages/spec/CHANGELOG.md` 17.4.0,
commit `233222e`, under
`### Minor Changes` (heading at :5, entry at :60, no intervening
heading).
So the review's F4 comparison was drawn against the wrong entry, and
repeating it would have
compiled a second inaccurate justification into the shipped CHANGELOG —
the exact defect F4
names. The sentence now names #16041, its grade and where to read it,
and disposes of #16322
explicitly so the next reader does not re-litigate it from the same
mis-reading. The ruling is
executed (the justification is corrected), not reopened (the level is
not touched).
### F1 — a sentence that ships to npm was false
**Door measurement taken here, not adopted from the review.**
`datasetSelectionRefusal` driven
directly over the built dist:
| `dateRange` | status | code | message ends |
|:--|--:|:--|:--|
| `['2026-01-01']` | 400 | `ANALYTICS_DATE_RANGE_UNRECOGNIZED` | `…
received a 1-element array, not the two bounds [start, end]. Refused at
the schema (…)` |
| `[]` | 400 | same | `… received an empty array … Refused at the schema
(…)` |
| `['a','b','c']` | 400 | same | `… received a 3-element array … Refused
at the schema (…)` |
| `['2026-01-01','2026-01-31']` | — | — | passes (`undefined`) |
Mechanism confirmed in source: `analytics-selection-door.ts` parses the
selection's shared
members — `timeDimensions` included — against
`AnalyticsQuerySchema.pick(…)`, and
`rest-server.ts:10981` calls it before `queryDataset`. ⇒ the one route
the TSDoc named as a
"past the door" origin is a **schema-origin door**.
Corrected at four sites, all naming the in-process
`AnalyticsService.query` / `queryDataset` /
dataset-executor path as the runtime origin and stating that every REST
analytics route is a
schema-origin door:
1. `packages/spec/src/data/analytics.zod.ts` — the shipped one
(`files[]` carries
`src/**/*.zod.ts` literally).
2. `packages/core/src/utils/analytics-date-range.ts` Reachability —
pre-existing on the base,
carried per the ruling.
3. `packages/core/src/utils/analytics-date-range.ts` "Every refusal
raised here is raised PAST
the schema door" — this PR's own new prose, which the review flagged as
untrue of the REST
dataset door. Narrowed to refusals whose MESSAGE leaves the constructor,
with the one caller
that keeps only `.code`/`.status` named.
4. the `service-analytics` `objectql-daterange.test.ts` comment.
### F2 — one condition, two wordings on the wire
**Premise check first (falsifiable, and it holds):** on the base the arm
was
`z.array(z.string())` with no length constraint, so a 1-element array
was not refused by the
schema at all. No schema refusal, no second entry ⇒ this PR introduces
the second wording, and
it is this PR's to collapse.
**Wire measurement, before:** on the dataset door, `['2026-01-01']`
served
`code: ANALYTICS_DATE_RANGE_UNRECOGNIZED` with a message ending
`… Refused at the schema (ANALYTICS_DATE_RANGE_UNRECOGNIZED / 400).;
selection.timeDimensions.0.dateRange: Too small: expected array to have
>=2 items`
— the prescription, then zod's restatement of the very arity it
prescribes. A body wrong in
more than the dateRange carried the same pair as two `fields[]` entries,
so the defect was not
confined to the lifted path.
**Wire measurement, after** (same probe, same tree): `Too small` / `Too
big` occurrences = 0
for `['2026-01-01']`, `[]` and `['a','b','c']`; prescription occurrences
= 1; the legal window
and the preset still pass; and the mixed body still carries
`selection.measures` beside exactly
one `selection.timeDimensions.0.dateRange` entry.
**Where the collapse lives, and why there.** In `fieldsFromZodIssues`
(`packages/types/src/validation-failure.ts`), keyed on the exported
structural recogniser
`isAnalyticsDateRangeRefusalIssue`. Both REST doors report through that
one mapper — the runtime
`/analytics/query` door already did; the dataset door now does too,
instead of reading the raw
D3 `zodIssuesToFields` — so the rule has ONE implementation. The
alternatives were each worse:
a copy at both doors is the two-copies-of-one-policy shape #8124/#8318
exist to end; the
selection policy in `shared/union-branch-policy.ts` is shared with the
terminal renderer and
pinned against it by a parity test, so a rule added there would have to
be schema-agnostic; and
a schema-agnostic rule ("drop branch issues at the union's own path")
silences the `#4001`
unknown-key prescriptions, which are raised at exactly that position. A
new exported helper was
rejected too: T3 of `check-widening-tells.mjs` reads a new `api-surface`
row as a widening tell,
and this PR's claim declares `Clause-②: no`.
**What is deliberately NOT collapsed** — this narrows a restatement,
never a diagnosis:
- a branch issue naming a DEEPER position is kept. `['2026-01-01', 3]`
still reports
`selection.timeDimensions.0.dateRange.1` — WHICH bound is not a string
is a location the
prescription does not carry.
- every other union expands exactly as before. ⛔ Union-branch expansion
is not suppressed
wholesale, so the stop condition in the ruling was not reached.
**Pinned on the wire, in both directions** (the review recorded that
nothing pinned it either
way): 4 cases at the dataset door, 4 at `/analytics/query`, 5 in the
shared mapper's own suite —
including a CONTROL that a branch issue at its own branch root
(`unrecognized_keys`, the same
structural position) still reaches the wire for every other key.
`@objectstack/types` and `@objectstack/rest` join the changeset: both
publish a changed wire.
### Verification on `ae337c01a9`
- Derived gate union (`scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`,
re-derived on the final head): **113 of 115 exit 0.**
- The other 2 are `PREREQUISITE NOT MET`, exit 3, printing "⛔ This is
NOT a pass: nothing was
measured" — `check:dual-build-cjs-loads` and `check:type-check-debt`,
both of which need the
whole monorepo built. **NOT MEASURED**, not red; CI builds fresh.
`check:skill-examples` refused the same way at first and was cleared by
building
`client-react` / `client`: 258 examples over 3 surfaces, exit 0.
- `pnpm lint` (repo-wide, `eslint . --no-inline-config`): exit 0 — the
whole population, so no
narrowing to justify.
- Full suites, all exit 0: spec 13624 · runtime 3644 · rest 3201 (+1
skipped) ·
service-analytics 2392 · core 1316 · types 671.
- `typecheck` exit 0: spec, core, types, rest, runtime;
`service-analytics tsc --noEmit` exit 0
with 0 errors (the review saw 38 there, all from unbuilt workspace deps
in its worktree).
- `check:type-source-resolution` exit 0 (135 tsc programs, 80 packages)
— the new
`@objectstack/spec/data` import in `packages/types` is a fifth subpath
on a dependency that
package already declares.
- `dispatch-gates` prints a STALE TREE warning: its one stale input is
`scripts/pm/check-harness-current.mjs`, added on `main` after this
branch, whose own header
says "⛔ not a CI gate" — so it adds no family here.
### Acceptance notes (noted, not filed)
-
`packages/services/service-analytics/src/__tests__/analytics-date-range-conformance.test.ts:32`
carries the same "which types its selection from `AnalyticsQuery` and
NEVER Zod-parses it"
sentence about `POST /analytics/dataset/query`. It is pre-existing (not
in this diff) and the
ruling scoped this round to F1 + F2 + F4 naming three sites, so it is
left alone. Not shipped
prose — a test comment. Carrier: the next PR in this family, or #18232.
---
_Generated by [Claude Code](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent eac58c3 commit 75237a9
20 files changed
Lines changed: 892 additions & 52 deletions
File tree
- .changeset
- content/docs/references
- api
- data
- ui
- packages
- core/src/utils
- rest/src
- runtime/src
- services/service-analytics/src/__tests__
- spec
- scripts
- lib
- src
- data
- migrations
- entries/semantic
- types/src
| 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 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
84 | 84 | | |
85 | 85 | | |
86 | 86 | | |
87 | | - | |
| 87 | + | |
88 | 88 | | |
89 | 89 | | |
90 | 90 | | |
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
60 | 60 | | |
61 | 61 | | |
62 | 62 | | |
63 | | - | |
| 63 | + | |
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
| 101 | + | |
102 | 102 | | |
103 | 103 | | |
104 | 104 | | |
| |||
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
113 | | - | |
| 113 | + | |
114 | 114 | | |
115 | 115 | | |
116 | 116 | | |
| |||
0 commit comments