Repository navigation
fix(UndefinedObject, TypeSystem): section objects in theme blocks and theme-mode snippets, shared contextual lists (upstream 944, partial) - #2
Conversation
…ve_search' in doc'd snippets and untyped section in snippets (Shopify#944)
…de snippets and type section/block/predictive_search there (Shopify#944)
arman-dogru
left a comment
There was a problem hiding this comment.
Peer Reviewer: CHANGES REQUESTED (bb4a84c)
Counts: 0 blocker · 1 major · 5 minor · 3 nit
The code change is small and correct for what it does. All the evidence reproduces, byte for byte (PNG included), and upstream has no competing work. The one major finding is about the design rule, not the code: the PR's own justification applies equally to recommendations, which it still reports as a "true positive".
Findings
1. MAJOR: predictive_search and recommendations are treated differently under the same rule
- Where:
packages/theme-check-common/src/checks/undefined-object/index.ts:222andpackages/theme-language-server-common/src/TypeSystem.ts:358. Also the reprorepro-theme/snippets/true-positives.liquid:2-4,11and PR body lines 16, 20, 54 and 104. - What the PR says: it allows
predictive_searchin theme snippets based on therenderrule ("any objects that are directly accessible outside the snippet"). - Why that also covers
recommendations:- shopify.dev uses the same restriction for both objects.
predictive_search"returns results only when rendered in a section using the Predictive Search API and the Section Rendering API".recommendations"returns products only when rendered in a section using the Product Recommendations API and the Section Rendering API". - Both are in the same
sections/*contextual list (index.ts:243,TypeSystem.ts:385). - So by the PR's own rule, a snippet rendered from the recommendations section can read
recommendationsexactly as one rendered from the predictive search section can readpredictive_search.
- shopify.dev uses the same restriction for both objects.
- The evidence contradicts open question 1. The PR labels
recommendationsa true positive in the repro snippet, the before/after table and the PNG ("still reported"), while open question 1 argues it is readable. - The cited upstream precedent cuts the other way. Upstream's reasoning in Shopify#1267 (d4ec896) was "
recommendationsstays section/block-only and is still flagged in theme-mode snippets". Under that reasoning,predictive_searchis equally section-only. The body cites "upstream's existing test" forrecommendationsbut never engages with Shopify#1267's reasoning, which applies topredictive_searchtoo. - Repro:
snippets/x.liquid={% doc %} @param {string} x - x {% enddoc %}{{ predictive_search.terms }}{{ recommendations.products_count }}. On the branch, the first gives 0 offenses and the second gives 1. I ran this throughrunLiquidCheckand the repro capture. - Fix: pick one rule and apply it to both objects.
- (a) Allow
recommendationsin theme snippets too. Update the upstream test with the shopify.dev citations, add it to the LS list, and stop calling it a true positive in the repro, table and PNG. - (b) Or document a concrete semantic difference that justifies the split. I couldn't find one.
- Either way, the evidence must not label as "true positive" something the PR itself argues is valid.
- (a) Allow
2. MINOR: the LS half of the change has no visual proof (standing rule: every important doc claim needs visual support)
- The title, changeset and body claim that "the language server types and completes section/block/predictive_search" in snippets. The only support is unit tests. The PNG and captures cover only
UndefinedObject, and the upstream PR template asks for before/after screenshots under "Tophatting". - Repro: I ran
CompletionsProvider/HoverProviderwith the realtheme-check-docs-updater/datadocset (a throwaway probe spec, deleted afterwards) on a theme-modesnippets/s.liquid:{{ pre█ }}: base[], branch["predictive_search"].{{ predictive_search.█ }}: base[], branchperformed, resources, terms, types.{{ section.█ }}: base 0 props, branch 6 props.- Hover on
predictive_search: base null, branch shows the docs. {% for p in predictive_search.resources.products %}{{ p.█ }}: base nothing, branch product props.
- The behaviour is right. The evidence just isn't in the PR.
- Fix: add a reproducible LS capture (script plus before/after image, or an editor screenshot) to the PR body.
3. MINOR: open question 2. The two copies are in sync today, but sharing them only by comment is the mechanism that already failed
- Where:
index.ts:222(+ comment:259-260) vsTypeSystem.ts:358. The lists are in sync now: both are['app','section','block','predictive_search'], and the sections, blocks and checkout lists also match. - Why it matters: this PR exists because Shopify#1267 changed only the check and the LS silently drifted. The new TypeSystem test hardcodes the list instead of importing it, so a future drift would still pass CI.
- The comment also points one way only.
TypeSystem.tsdoes not say "keep in sync with theme-check-common". theme-language-server-commonalready depends on@shopify/theme-check-common(package.json:31, imported atTypeSystem.ts:36), so sharing is cheap.- Fix (preferred): export the contextual-object constants (at least
BLOCK_CONTEXTUAL_OBJECTSandTHEME_SNIPPET_CONTEXTUAL_OBJECTS) from theme-check-common and import them in TypeSystem. Leave the path matching as is, since one side usesstartsWithand the other a regex. - Minimum fix: add a parity test that imports the check's list.
4. MINOR: the PR body and title overstate or misdescribe the change
- Line 1, "Fixes the remaining part of Shopify#944". In the Shopify#944 thread, MarkRijkelijkhuizen asked for
formandpaginatetoo, and this PR deliberately keeps those reported. The deliberate choice is right (finding 6 explains why), but the body should say Shopify#944 stays partly open, or state that the form/paginate request is invalid and why, rather than "fixes the remaining part". An upstreamFixes #944would auto-close the thread. - The title says "section/block typing in doc'd theme snippets". The TypeSystem change applies to every theme-mode snippet, with or without
{% doc %}. My probe used a snippet with no doc tag and got the completions. The mermaid edge from F3 to T implies the doc gate applies to the LS too, which it doesn't. - "conflicts with upstream d4ec896" (line 70). Nothing conflicted: d4ec896 never touched the LS and the old test kept passing. The accurate wording is "left the LS inconsistent with the check".
- "nothing posted upstream" (line 1). Upstream Shopify#944's public timeline now shows two
cross-referencedevents by arman-dogru, from fork PR #2 and PR #1, at 8:31 AM EDT today. The issue/PR links did post visibly upstream. My earlier PR #1 round checked for exactly this ("no owner-qualified upstream refs"). - Fix: rewrite line 1, the title and line 70. Use non-linking text for upstream refs on the fork (for example
Shopify/theme-toolsissue 944 in backticks). The two events already posted will likely stay.
5. MINOR: the branch isn't upstream-ready as is (tree contents and commit history)
- The
docs/fixes/944-predictive-search-snippets/tree is out of place upstream. It adds 17 files: a 73 KB PNG, a Python script that needs matplotlib, captures and a repro theme. Upstream has nodocs/fixesconvention (docs/holds only ADRs and guides) and no.pyfiles anywhere. - The failing-test commit would land in upstream history. Upstream merges with merge commits (ee5ab68, 20f8edf), so the intentionally red
dfb8f3fwould land on main and breakgit bisect. - Fix: for the upstream PR, squash
dfb8f3finto4305404. Move the evidence into the PR description (uploaded images plus collapsed captures) and keepdocs/fixes/on the fork branch only. Use upstream'sPULL_REQUEST_TEMPLATE.mdsections ("What are you adding", "What's next", "Tophatting" checklist, patch changeset box).
6. MINOR: theme blocks still report predictive_search, which the same rule doesn't support, and the PR doesn't mention it
- Where:
index.ts:246-247(blocks/→BLOCK_CONTEXTUAL_OBJECTShas nopredictive_search) andTypeSystem.ts:387-388. - Repro:
blocks/a.liquid={{ predictive_search.terms }}gives 1 offense, while a doc'd snippet gives 0. - Under the PR's render rule, a theme block placed in the predictive-search section via
content_for 'blocks'has the same access a snippet does. This is the same reason upstream allowsrecommendationsin blocks; Horizon'sblocks/product-recommendations.liquidreads it. So a snippet rendered from that block passes, but the block itself fails. - Fix: either add
predictive_searchto the block list (scope creep; fine as a follow-up), or list "theme blocks" under "What still gets reported, on purpose" with the reason.
7. NIT: the whole-repo test count doesn't reproduce
- Claimed: "268 files passed (1 skipped), 7168 passed, 5 skipped".
- What I got:
CI=true vitest rungives 274 passed | 1 skipped (275) files, 7195 passed | 5 skipped (7200), 0 failed, on both Node 20.19.2 and 22.23.4. The CI matrix is Node 22/24, not the 20 used in the body. - The per-package numbers match exactly, so this is a stale or partial run, not a hidden failure.
- Fix: update the number and run on a CI Node version. The fork has Actions enabled but no runs, so enabling workflows on the fork would give real CI.
8. NIT: capture.sh robustness
- Moving base ref:
BASEdefaults toorigin/main(capture.sh:10), which is a moving ref. Once fork main syncs with upstream, "before" silently changes. Default toee5ab68. - No restore on failure: there is no
trapto restore the tree if a step fails after the base checkout. - Destructive checkout:
git checkout -q HEAD -- packages(:14) discards any local edits underpackages/. That's documented as "clean tree", but agit diff --quietguard would be safer. - Sort order:
offenses.mjs:12sorts lexicographically, so:10comes before:9in the captures and PNG.
9. NIT: formatting
prettier --check docs/fixes/944-predictive-search-snippets/offenses.mjs→[warn] Code style issues. It's outsideformat:check's glob, so CI won't catch it.
Developer's three questions
- (a) Replacing the March 2026 test: legitimate.
- 6951379 (Gray Gilmore, closes Shopify#346) added "should not complete block-level contextual variables in snippets when in theme mode". At that time the check also reported
sectionin theme snippets (return ['app']), so the test encoded consistency between LS and check. - Shopify#1267 / d4ec896 (NathanPJF, approved by graygilmore, who was "a little torn… probably a reasonable trade off") changed only the check, in 3 files, with no LS discussion.
- So the replacement restores the original invariant rather than reversing a decision. The new negative test (
recommendationsnot completed) keeps the guard. - Wording fix: see finding 4.
- 6951379 (Gray Gilmore, closes Shopify#346) added "should not complete block-level contextual variables in snippets when in theme mode". At that time the check also reported
- (b) form/paginate stay reported: correct.
- Liquid's
renderusescontext.new_isolated_subcontext(Shopify/liquidlib/liquid/context.rb:146-162,tags/render.rb:63). It keeps only static environments and registers, not scopes.formandpaginateare tag-scoped (index.ts:106-110). - Dawn 16 passes
paginate: paginateexplicitly (snippets/pagination.liquid:6,quick-order-list.liquid:103,facets.liquid:12). recommendationsis the inconsistency: see finding 1.
- Liquid's
- (c) The two lists: in sync now, and a shared constant is warranted. See finding 3.
Upstream status (checked today)
- Shopify#944: open, Bug label, no assignee. It was assigned 2025-05-15 and unassigned 2025-07-03.
- No upstream PR is working on Shopify#944. Of the 78 open PRs, those touching
undefined-object/TypeSystem/ObjectCompletionare Shopify#1347, Shopify#1343, Shopify#1310, Shopify#1298, Shopify#1238, Shopify#1197 and Shopify#1184. Of those, Shopify#1238 and Shopify#1197 edit the touched files, and none touches the snippet contextual lists. - Searches: a search for "predictive_search" returns only 2023 PRs (Shopify#155/Shopify#159/Shopify#160).
- Merge check: a trial merge of upstream/main (20f8edf, 10 commits ahead of base) is clean, and none of the touched files changed upstream since ee5ab68.
- Shopify#608:
00b683ec(authored 2024-11-24, committed 2024-11-26) refactored valid-local-blocks. A footer schema with@appplus local blocks usinginline_richtext/link_list/header/paragraph/checkboxsettings gives 0 offenses on this tree. The claim holds. - Shopify#722:
theme-check-docs-updater/data/section.json→properties.locales={object → additionalProperties {object → additionalProperties {type: string}}}, from theme-liquid-docs. The claim holds. - CLA: upstream runs
Shopify/shopify-cla-action(.github/workflows/cla.yml). Arman will need to sign it when he opens the upstream PR.
What I verified (fresh clone, bb4a84c exactly)
| Claim | Claimed | Mine |
|---|---|---|
Install/build per CONTRIBUTING (pnpm install --frozen-lockfile, pnpm build), pnpm 10.16.1 |
n/a | OK. The submodule syntaxes needs an https rewrite for ssh-less boxes, an environment issue only. |
| Repro theme before → after | 7 → 5 | 7 → 5. Both predictive-search-results.liquid 7:25/8:22 are gone. layout 6:8, form, paginate, recommendations and undefined_thing remain. |
capture.sh deterministic |
identical | Ran twice: diff -r empty. All 4 txt files and before-after.png are byte-identical to the committed ones (sha256 80cbad61…e990). |
| Dawn 16.0.0 (bc39a7d), all checks | 9 / 9 | 9 / 9, identical lines, 3 UndefinedObject |
| Horizon (dfad90d, 134 doc'd snippets), all checks | (not claimed) | 6 / 6 identical, 0 UndefinedObject |
| 3 touched specs on main sources + new tests | 3 failed / 179 / 1 skipped | 3 failed / 179 passed / 1 skipped (183), same 3 tests |
| 3 touched specs on branch | 182 + 1 skipped | 182 passed / 1 skipped (183) |
| Skipped test | (not explained) | TypeSystem.spec.ts:713 it.skip('should support narrowing the type of blocks'), a // TODO from 78813ea (2023-11-02). Pre-existing, unrelated, so the skip is justified. |
| theme-check-common | 5363 | 121 files, 5363 passed |
| theme-language-server-common | 646, 1 skipped | 75 files, 646 passed, 1 skipped |
Whole repo CI=true vitest run |
268 files / 7168 | 274 + 1 skipped files / 7195 passed, 5 skipped, 0 failed on Node 20 and 22 (finding 7) |
tsc --noEmit (both packages), pnpm type-check (all) |
clean | exit 0 / exit 0 |
pnpm format:check glob |
clean | clean (offenses.mjs outside the glob, finding 9) |
| Check edge cases (branch) | n/a | doc'd snippet 0 · non-doc snippet 0 (check skipped) · {% liquid echo %} 0 · app-mode snippet 1 · blocks/ 1 · sections/ 0 · templates/search 1 · assigned shadow 0 |
| LS edge cases (branch vs base) | n/a | See finding 2. Sections/blocks/layout/templates are unchanged. App-mode snippets are unchanged (pre → [], rec → recommendations). |
| Changeset | patch, both packages | Present and correct: patch for theme-check-common and theme-language-server-common, matching upstream's Shopify#1267/69513792 style |
| Commit authorship | Arman | All 4 commits have author and committer Arman Doğru <53595807+arman-dogru@users.noreply.github.com> and no extra trailers |
| SHA-pinned links | resolve | Every #L anchor lands on the stated line (index.ts:222, TypeSystem.ts:358, spec 359/380/390, TypeSystem.spec 784, ObjectCompletion 378/388) |
render docs quote (body line 16) |
n/a | Matches shopify.dev/docs/api/liquid/tags/render verbatim |
| Mermaid diagram | n/a | Matches create → onCodePathEnd → globalObjects → getContextualObjects → isDefined. Only the F3 → T doc-gate implication is off (finding 4). |
…me blocks and snippets, shared lists (Shopify#944 review) - predictive_search, recommendations and comment (the section objects) are readable in theme blocks and theme-mode snippets, per the render rule. - Export the contextual-object lists from theme-check-common and use them in TypeSystem; parity tests import the same lists. - App-mode blocks and snippets are unchanged.
arman-dogru
left a comment
There was a problem hiding this comment.
Peer Reviewer: APPROVED (1584359)
Round 2. Counts: 0 blocker · 0 major · 2 minor · 3 nit. All 9 round-1 findings are resolved: 8 fixed and 1 withdrawn.
I re-ran everything on a fresh build of 1584359 (on top of bb4a84c, no force-push) and on the upstream variant ce74e16.
- One rule now:
contextual-objects.tsdefines the lists once and both consumers import them. - Evidence reproduces byte for byte, both PNGs included.
- No regressions: Dawn and Horizon give identical offenses.
- The upstream variant is a faithful squash.
The remaining items can be fixed before or while opening upstream.
Round-1 findings
| # | Round 1 | Status at 1584359 | How I checked |
|---|---|---|---|
| 1 | MAJOR predictive_search vs recommendations inconsistent |
Fixed | THEME_BLOCK_AND_SNIPPET_CONTEXTUAL_OBJECTS = ['app','block',...SECTION_CONTEXTUAL_OBJECTS] (contextual-objects.ts:40-44) feeds both index.ts:240-244 and TypeSystem.ts:379-383. Repro: recommendation-list.liquid:6:23/7:20 now gone. recommendations is no longer called a true positive (true-positives.liquid, tables, PNG). |
| 2 | MINOR no LS visual proof | Fixed | ls-capture.mjs runs 10 probes with the built CompletionsProvider/HoverProvider. My two runs produced TSV and PNG byte-identical to the committed ones. The PNG matches the TSV cell for cell, and both alt texts describe the images accurately. Small gaps: n1, n2, m2. |
| 3 | MINOR lists synced only by comment | Fixed | Exported from theme-check-common/src/index.ts:54 (export * from './contextual-objects'). It's present in dist/index.js:59 / dist/index.d.ts:7, and require('dist/index.js').THEME_BLOCK_AND_SNIPPET_CONTEXTUAL_OBJECTS resolves. contextual-objects.ts has no imports, so it can't create a cycle (madge's 2 cycles are pre-existing types.ts ones). The parity tests it.each over the imported lists (TypeSystem.spec.ts:800-811). pnpm build and pnpm type-check (all packages) exit 0. |
| 4 | MINOR body/title overstated | Fixed | Title says "upstream 944, partial". The body says "Refs, not Fixes", the doc gate is disclosed (line 30) and "left the LS inconsistent" is used (line 116). Upstream refs are code spans; I grepped for Shopify/theme-tools#, issues/, pull/ and #NNN and the only hit is the fork-local #1. The PR #1 comment has no upstream link. The upstream Shopify#944 timeline still shows only the 2 original cross-ref events (8:31 AM EDT) and no new ones; those can't be removed. |
| 5 | MINOR not upstream-ready | Fixed (via separate branch) | See "Upstream variant". The fork keeps docs/fixes/, and its README now says it's fork-only. |
| 6 | MINOR blocks still flagged predictive_search |
Fixed | blocks/predictive-search-terms.liquid:2:7 is gone. My own probe: blocks/a.liquid {{ predictive_search.terms }} → 0 in theme mode, 1 in app mode. |
| 7 | NIT whole-repo count | Withdrawn, my error | The 6 packages/codemirror-language-client/dist/esm/extensions/*.spec.js files that pnpm build emits hold exactly 27 tests (6+3+2+3+5+8) in my round-1 JSON report. 7195 − 27 = 7168 and 274 − 6 = 268, so Developer's round-1 number was right and my count included compiled duplicates. |
| 8 | NIT capture.sh | Fixed | BASE=${BASE:-ee5ab68} (:11). The dirty-tree guard works: I dirtied a tracked file and got exit 1 with the message. The trap works: a copy with false injected after the base checkout exits 1 and leaves git status clean. The sort is numeric (:8 < :9 < :10). |
| 9 | NIT prettier on offenses.mjs | Fixed | prettier --check docs/fixes/…/*.mjs is clean |
Developer's scrutiny points
- (a) Is widening the rule to
commentand the other section objects justified?predictive_search/recommendations: yes. shopify.dev gives both the same restriction, and therenderrule applies to both.sectionin blocks: not a regression. It was already allowed in blocks on base (BLOCK_CONTEXTUAL_OBJECTS), so nothing that used to be flagged is newly silenced there.comment: justified, but for a different reason than the PR says (m2).docs/objects.jsongivescommentno template access, onlyparents: article.comments. Upstream added it to sections in ec1fbd7 (Shopify#352, fixing Shopify#351) because "when you post anew_commentform, thecommentobject becomes available globally". That is the stronger argument, and Shopify's own Horizon relies on it:snippets/blog-comment-form.liquid:6declares@param {comment} [comment]as an optional workaround and readscomment.statusat:53, butsections/main-blog-post.liquid:51renders it without passingcomment.- Can it hide real bugs? Yes, in one case: a doc'd snippet rendered from a template or layout that reads a section object is no longer flagged. That's the same trade-off Shopify#1267 accepted for
section/block, and it's disclosed. - Real-theme deltas: Dawn 16 gives 9 → 9 offenses, identical, 3 → 3 UndefinedObject. Horizon dfad90d gives 6 → 6, identical, 0 → 0.
- (b) Do the widened theme blocks go beyond Shopify#944? Yes. Shopify#944 is about snippets. It is disclosed in the title, changeset, fork body table (line 19) and upstream body (lines 8 and 10), with a consistent rationale. The risk is that graygilmore was "a little torn" even about
section/blockin Shopify#1267, so maintainers may want blocks split out. Not a finding, but worth one line in the upstream "What's next". - (c) Is the new module exported and importable without circular dependencies? Yes; see row 3.
- (d) Is the LS visual proof reproducible? Yes. Two runs are byte-identical, the PNG matches the TSV, and the alt text is accurate; see row 2, n1 and n2.
- (e) Is the upstream variant correct? Yes; see below.
- (f) Are there linking upstream refs left? No; see row 4.
Upstream variant ce74e16 (fetched read-only from /home/box/work/theme-tools into refs/review/up944)
- History: exactly 1 commit (
20f8edfc..ce74e16), parent20f8edfc1ee4…(upstream main). - Authorship: author and committer are both
Arman Doğru <53595807+arman-dogru@users.noreply.github.com>. - Diff:
diff <(git diff ee5ab68 1584359 -- packages .changeset) <(git diff 20f8edf ce74e16 -- packages .changeset)→ identical, including blob hashes. There are no files outsidepackages/and.changeset/(nodocs/fixes), and the changeset is present. - Fresh worktree (
pnpm install --frozen-lockfile,pnpm build):pnpm type-check0,pnpm format:checkclean. The 3 specs give 187 passed / 1 skipped. The whole repo gives 278+1 files / 7210 passed / 5 skipped / 0 failed. That count includes the 6 dist copies (27 tests), so source-only is 7183, which matches the body's claim. - Body (
/workspace/theme944-upstream-body.md):- It follows
.github/PULL_REQUEST_TEMPLATE.md: What are you adding / What's next / What did you learn / Tophatting with the screenshot box / Before you deploy with the patch changeset box. - It uses "Refs Shopify#944. This doesn't close it", which won't auto-close.
- Other refs (Shopify#1267, Shopify#346, Shopify#352) are same-repo, non-closing mentions.
- Line 24's
commentrationale is wrong (m2), and the changeset box is debatable (m1).
- It follows
New findings
m1. MINOR: patch changeset for a public API addition
- Where:
.changeset/undefined-object-predictive-search-snippets.md:2('@shopify/theme-check-common': patch) and the same ince74e16. - Repro:
export * from './contextual-objects'(packages/theme-check-common/src/index.ts:54) adds 4 exported constants to the package's public entry point. Upstream's PR template says "Public API changes, new features → I included a minor bumpchangeset". - Fix: either bump
@shopify/theme-check-commontominor, or say in the upstream body why patch is acceptable (internal-use constants) and let maintainers decide. The LS package stayspatch.
m2. MINOR: the comment rationale is wrong, and comment has no visual evidence
- The rationale. Fork body line 23 and upstream body line 24 say
commentis in the section list "because of the article template (352)". Upstream's actual reason (ec1fbd7, Shopify#352) is thatcomment"becomes available globally" after anew_commentform post. - No visual evidence. Neither the repro theme nor
ls-capture.mjsexercisescomment. Only unit tests cover it (via theSECTION_CONTEXTUAL_OBJECTSloop). Under the standing rule, an important doc claim needs visual support. - Fix:
- Correct the rationale in both bodies.
- Cite the Horizon evidence above (
snippets/blog-comment-form.liquid:6,53rendered fromsections/main-blog-post.liquid:51withoutcomment). - Add a
commentcase to the repro theme (e.g. a doc'd comment-form snippet) and one LS probe ({{ comment.█ }}in a snippet), then regenerate.
n1. NIT: ls-capture.mjs depends on the machine's docs cache
- Where:
ls-capture.mjs:56usesnew ThemeLiquidDocsManager(). Its loaders read~/.cache/theme-liquid-docs-nodejsfirst and fall back to the bundledtheme-check-docs-updater/dataonly if that's missing (themeLiquidDocsManager.ts:138-142). - Today the captures are byte-identical and the cache equals the bundled data: both are revision
40f966fc…, andobjects.jsoncmp is identical. But "real Shopify docset" captures can drift on another machine or after the cache refreshes. - Fix: point the script at the bundled data, e.g. run with
XDG_CACHE_HOME=$(mktemp -d), or loaddata/*.jsondirectly.
n2. NIT: LS capture presentation
- H2 is labelled a target but behaves as a control. It's the hover on
recommendationsin a block, which is identical before and after because blocks already allowed it. The README says "8 targets, 2 controls"; it's effectively 7 and 3. - Hover text is clipped in the PNG. The
### predictive_search: \predictive_search`andrecommendations` strings are cut at the right cell edge. - Fix: relabel H2 and wrap or shorten the hover cells.
n3. NIT: the exported lists are mutable at runtime
- Where:
contextual-objects.ts:10,21,40,47. readonly string[]is a type-only guarantee. A JS consumer of the new public export canpushinto the arrays, which would change both the check and the LS.- Fix:
Object.freeze([...]), oras constplus freeze.
What I verified
| Claim | Claimed | Mine |
|---|---|---|
| Install/build | n/a | lockfile unchanged since bb4a84c; pnpm install --frozen-lockfile 0, pnpm build 0 |
| Repro theme | 10 → 5 | 10 → 5. Gone: block 2:7, snippet 7:25/8:22, recommendation-list 6:23/7:20. Kept: layout 6:8, template 2:4, form/paginate/undefined_thing. |
capture.sh twice |
byte-identical | Identical across runs and to the committed txt/tsv/PNGs (git status clean after both runs) |
| LS probes | 10 (2 controls) | C1–C6 and H1 change from none/null to the claimed values. H2, K1 and K2 are unchanged (n2). |
| Dawn 16.0.0 / Horizon dfad90d | 9/9, 6/6 | 9/9, 6/6, identical lines, UndefinedObject 3/3 and 0/0 |
| 3 touched specs, base sources | 6 failed | 6 failed / 181 passed / 1 skipped (188): same 6 tests |
| 3 touched specs, branch | 187 + 1 skipped | 187 passed / 1 skipped (188) |
| theme-check-common | 5364 | 121 files, 5364 passed |
| theme-language-server-common | 650 + 1 skipped | 75 files, 650 passed, 1 skipped |
| Whole repo | 7173 / 5 skipped, Node 20/22/24 | 7200 passed / 5 skipped incl. 6 dist copies (27 tests). Source-only 268+1 files, 7173 passed, 5 skipped, 0 failed. Same on Node 20.19.2, 22.23.4 and 24.22.1. |
| type-check / prettier | clean | pnpm type-check 0; format:check glob clean; docs *.mjs and changeset clean |
Upstream ce74e16 |
1 commit, no docs/fixes, 7183 | Confirmed (see above) |
| SHA-pinned links | resolve | contextual-objects.ts#L10-L45, index.ts#L240, TypeSystem.ts#L371-L383, index.spec.ts#L363/L384/L394/L409, TypeSystem.spec.ts#L786-L836, ObjectCompletionProvider.spec.ts#L378/L390 all land on the stated code |
| Commits | Arman | eb5292a, b8b8bfa, 86570f1, 1584359: author and committer Arman Doğru <53595807+arman-dogru@users.noreply.github.com> |
…e the lists, comment rationale + test
…pro/probes, K3 control, wrapped PNG cells
arman-dogru
left a comment
There was a problem hiding this comment.
Peer Reviewer: APPROVED (9272f1a)
Round 3. Counts: 0 blocker · 0 major · 0 minor · 2 nit. All 5 round-2 findings are fixed.
I rebuilt from scratch at 9272f1a (4 commits on top of 1584359, no force-push). The suites pass on Node 20/22/24 and the captures are byte-identical across two runs and to the committed files. The upstream squash 4ba504e has the same package and changeset diff as the fork. The two nits below are optional polish.
Round-2 findings
| # | Round 2 | Status at 9272f1a | How I checked |
|---|---|---|---|
| m1 | MINOR: patch changeset for new public exports | Fixed | .changeset/undefined-object-predictive-search-snippets.md:2 is '@shopify/theme-check-common': minor, and the LS stays patch. The text names all 4 exports. Upstream body line 76 ticks the template's "Public API changes → minor" box. |
| m2 | MINOR: wrong comment rationale, no visual evidence |
Fixed | See the comment notes under this table. |
| n1 | NIT: LS capture depended on the machine cache | Fixed (caveat in new nit 2) | ls-capture.mjs:16-32 reads only packages/theme-check-docs-updater/data/*.json (no ThemeLiquidDocsManager, no cache) and prints # docset 40f966fc… as the first TSV line. render_capture.py:51-52 skips that line. |
| n2 | NIT: H2 mislabelled, PNG clipped | Fixed | The old H2 is now control K3 (hover recommendations in a block, docs → docs). The PNG title says "C*/H* are targets, K* are controls". Cells wrap at 30/40 characters, and I saw no clipping in the regenerated PNG. |
| n3 | NIT: mutable exported lists | Fixed (test gap in new nit 1) | All 4 lists are Object.freeze([...]) (contextual-objects.ts:15,26,45,52). dist: Object.isFrozen is true for all 4, and push throws TypeError. |
comment (m2)
- Rationale:
contextual-objects.ts:9-14, fork body line 23 and upstream body line 24 now cite ec1fbd7/Shopify#352 ("globally available once anew_commentform is posted") plus Horizon. - New check test:
index.spec.ts:409fails on base. It is one of the 7 base failures intests-before.txt. - Repro:
snippets/blog-comment-form.liquid:8:10is gone, and the PNG and table show it. - LS probes:
- C7
{{ comment.█ }}inside anew_commentform goes from none toauthor, content, created_at, email, id, status, updated_at, url. - H2 hover goes from null to
### comment.
- C7
Points you asked me to verify
Object.freezedoesn't break consumers, and the types work.- The only consumers are
globalObjects→contextualObjects.includes(name)(index.ts:194-198) andcontextualVariables→contextualEntries.includes(...)(TypeSystem.ts:349-350). A grep forpush|pop|splice|sort|reverse|shift|unshift|fill|copyWithinon these lists acrosspackages/*/srcreturns nothing. - Spreading inside
THEME_BLOCK_AND_SNIPPET_CONTEXTUAL_OBJECTScopies before freezing.[...list, 'x']andlist.concat(['y'])still work on the frozen exports (5 and 5). Object.freeze(string[])returnsreadonly string[], matching the annotations.pnpm type-check(all packages) andpnpm buildboth exit 0.
- The only consumers are
- The
commentrepro matches Horizon's real render pattern.- Horizon:
sections/main-blog-post.liquid:51{% render 'blog-comment-form', article: article, section_id: section.id %}. The snippet readscomment.status(:53) inside{% form 'new_comment', article %}(:10) without being passedcomment. - Repro:
sections/main-article.liquid:1{% render 'blog-comment-form', article: article %}. The doc'd snippet readscomment.statusinside{% form 'new_comment', article %}. - The pattern matches. The repro deliberately omits Horizon's optional
@param {comment} [comment]workaround, which is why Horizon itself shows 0 offenses before and after.
- Horizon:
- The new LS probes are correct and the PNG matches the TSV.
- C7 and H2 behave as described above.
- Targets (C1–C7, H1, H2): 9 rows, all changed and shaded. Controls (K1–K3): 3 rows, unchanged and unshaded.
- Every PNG cell matches
ls-before.tsv/ls-after.tsv.
4ba504eis a faithful single commit for upstream.- History: exactly 1 commit on
20f8edfc. Author and committer are bothArman Doğru <53595807+arman-dogru@users.noreply.github.com>. - Diff:
diff <(git diff ee5ab68 9272f1a -- packages .changeset) <(git diff 20f8edf 4ba504e -- packages .changeset)is identical. There are no paths outsidepackages/and.changeset/. - Clean worktree build: install, build and
type-checkexit 0, andformat:checkis clean. On Node 20 and 22 the run gives 278+1 files / 7212 passed / 5 skipped, 0 failed. Without the 6 dist copies that's 7185 passed / 5 skipped. - Body:
/workspace/theme944-upstream-body.mdhas all the template sections, including the "Public API changes" checklist. It uses "Refs Shopify#944. This doesn't close it" and offers to split out blocks (line 25). A grep forfix|close|resolve #N,Owner/repo#Nandgithub.com/Shopify/theme-tools/issues|pullfinds nothing. Plain Shopify#944/Shopify#1267/Shopify#346/Shopify#352 are same-repo mentions, which are fine upstream.
- History: exactly 1 commit on
- The fork PR body has no linking upstream refs.
- Upstream numbers and SHAs are in code spans. The only
#Nis the fork-local#1(line 162). - Upstream Shopify#944's timeline still shows only the 2 cross-refs from 8:31 AM EDT; nothing new.
- The SHA-pinned anchors
contextual-objects.ts#L9-L49andindex.spec.ts#L409/#L427land on the stated code.
- Upstream numbers and SHAs are in code spans. The only
New findings
Nit 1: the frozen-list test covers only 2 of the 4 lists
- Where:
index.spec.ts:427-431. It checksSECTION_CONTEXTUAL_OBJECTSandTHEME_BLOCK_AND_SNIPPET_CONTEXTUAL_OBJECTSonly. - Repro:
- Removing
Object.freezefromSECTION_CONTEXTUAL_OBJECTS→ 1 failed (caught). - Removing it from
CHECKOUT_LAYOUT_CONTEXTUAL_OBJECTS→ 51/51 pass (not caught). The same applies toAPP_BLOCK_CONTEXTUAL_OBJECTS.
- Removing
- Fix: loop over all 4 exports.
Nit 2: "bundled docset… no network" overstates the pinning
- Where:
ls-capture.mjs:3-4, README row, and fork body line 87. - What actually happens:
packages/theme-check-docs-updater/data/is gitignored (.gitignore:21, 0 tracked files). It is downloaded by that package'spostbuild(node scripts/cli.js download data) duringpnpm build. So the capture itself is offline, but the data comes from the network at build time. The revision is recorded in the TSVs (40f966fc…) but not enforced. - Fix: say "downloaded by
pnpm build". Optionally, havels-capture.mjsexit non-zero whenlatest.json's revision differs from an expected one, so a regenerated capture can't drift silently.
What I verified
| Claim | Claimed | Mine |
|---|---|---|
| Install/build | n/a | lockfile unchanged; pnpm install --frozen-lockfile 0, pnpm build 0 (docset revision 40f966fc…) |
| Repro theme | 11 → 5 | 11 → 5 (adds blog-comment-form.liquid:8:10 to round 2's 10) |
| LS probes | 9 targets changed, 3 controls unchanged | 9 changed (C1–C7, H1, H2), 3 unchanged (K1–K3) |
capture.sh twice |
byte-identical | identical across runs and to the committed txt/tsv/PNGs (git status clean) |
| 3 touched specs, base sources | 7 fail | 7 failed / 182 passed / 1 skipped (190) |
| 3 touched specs, branch | 189 + 1 skipped | 189 passed / 1 skipped (190) |
| theme-check-common / LS | (not restated) | 5366 / 650 + 1 skipped |
| Fork whole repo, Node 20/22/24 | 7175 / 5 / 0 (source-only) | source-only 268+1 files, 7175 passed / 5 skipped / 0 failed on 20.19.2, 22.23.4 and 24.22.1. That's 7202 with the 6 dist copies. |
Upstream 4ba504e |
7185 / 5, Node 22 | 7185 / 5 / 0 source-only (7212 incl. dist) on Node 20 and 22 |
| type-check / prettier | clean | pnpm type-check 0; format:check clean; docs *.mjs and .changeset/*.md clean |
| Dawn 16.0.0 / Horizon dfad90d | 9/9, 6/6 | 9/9, 6/6, identical lines |
| Freeze consumers | safe | no mutators; spread and concat fine; isFrozen true ×4 |
| Commits | Arman | 0425287, 3544eb9, 41fbf1f, 9272f1a: author and committer Arman Doğru <53595807+arman-dogru@users.noreply.github.com> |
… nested and app-mode tests - contextual-objects.ts, UndefinedObject, TypeSystem and the changeset now say blocks/snippets get *potential* access to section objects: the path can't tell who renders a snippet (e.g. a layout), matching the existing broad allowance for section/block. A test pins that known limitation. - Only the object lists are shared; file classification still differs (startsWith vs regexes), noted as a possible follow-up. - Regression tests for predictive_search.resources.products inference and completion in a doc-tagged snippet; negative app-mode inference/completion tests for predictive_search and comment; literal expectations, with one parity test kept on the exported lists.
…t), amber label in PNG
arman-dogru
left a comment
There was a problem hiding this comment.
Peer Reviewer: APPROVED (63afdb0)
Round 4 (final): Arman's review of the upstream branch, applied as new commits. Fresh clone at /workspace/review/theme_tools_pr2_r4; every git/gh call ran without GITHUB_TOKEN.
Result: 0 blocker, 0 major, 0 minor, 4 nits. Every number Developer claimed reproduced exactly. The nits don't block, and none of them touch the upstream branch's code.
Findings
1. nit: tests-before.txt isn't stable across capture runs (docs/fixes/944-predictive-search-snippets/capture.sh:33)
- Repro: I ran
capture.sh3 times on a clean tree. In one run,tests-before.txtlisted the failing spec files in a different order (undefined-object/index.spec.tscame beforeTypeSystem.spec.ts), a 5+/5- line diff. The other two runs were byte-identical to HEAD. The order follows vitest's completion order across theshared/isolatedprojects. - Fix: make the order deterministic, e.g. run each spec file separately and append in a fixed order, or sort the
❯ |project| fileblocks instrip(). Alternatively, say "byte-identical apart from file order" in the body.
2. nit: capture.sh overwrites the captures before it finds out matplotlib is missing, then refuses to rerun (capture.sh:16-19, :40)
- Repro: run it with a
python3that has no matplotlib. The.txtand.tsvfiles are rewritten, thenrender_capture.pyfails withModuleNotFoundError. The tree is now dirty, so the next run exits with "tracked files have uncommitted changes". - Fix: check
"${PYTHON:-python3}" -c 'import matplotlib'before the firstgit checkout, next to the dirty-tree check.
3. nit: the check-side "parity" test is a tautology (packages/theme-check-common/src/checks/undefined-object/index.spec.ts:406-410 vs contextual-objects.ts:56-60)
- The test asserts
THEME_BLOCK_AND_SNIPPET_CONTEXTUAL_OBJECTS == ['app', 'block', ...SECTION_CONTEXTUAL_OBJECTS], which is exactly how that list is defined, so the test can only fail if someone edits the definition itself. - The real cross-package guard is the
TypeSystem.spec.tsit.each(L786-835), which did fail under mutation (see below). - Fix: none needed. Optionally rename it to "theme snippet/block list = app + block + section objects", and in
docs/upstream-944-pr-body.md:20say "the TypeSystem parity test" rather than "one parity test per side".
4. nit (carried from round 3, still open): the freeze test covers 2 of 4 lists, and "bundled docset" wording remains
index.spec.ts:448covers onlySECTION_andTHEME_BLOCK_AND_SNIPPET_.- The
ls-before-after.pngtitle still says "(bundled docset)". The body text now explains it correctly ("written bypnpm build, revision 40f966f"). - Fix: add
APP_BLOCK_CONTEXTUAL_OBJECTSandCHECKOUT_LAYOUT_CONTEXTUAL_OBJECTSto the loop, and change the PNG title to "docset built by pnpm build".
Also a nit: PR body "Upstream-ready variant" links docs/upstream-944-pr-body.md by branch name (blob/fix/944-…), not by SHA. Every other link is pinned to 5ca4a59. Pin it to 63afdb0.
Verified
History and authorship
- Fork PR #2 head is
63afdb0c21017fb1524bb69f9a5ec8f1578d7860.9272f1ais an ancestor (no rewrite).- The new commits are
11c5042,0373749,5ca4a59and63afdb0(on top ofb2efdc4).
- The upstream branch is at
eace32587b81ba6d7db78683553db3aae0a87daa.4ba504eis an ancestor (no force-push).- Against Shopify
main20f8edfit is ahead 2 / behind 0, with 8 files (+480/−75). 11c5042andeace325have the samegit patch-id(a40adff…), so it is a clean cherry-pick. All 8 files are blob-identical to the fork head.
- Every commit since
9272f1aand sinceupstream/mainis authored and committed byArman Doğru <53595807+arman-dogru@users.noreply.github.com>. No Co-authored-by or tool trailers. No#NNNin the new commit messages. - Upstream:
gh pr list -R Shopify/theme-tools --author arman-dogru --state allreturns[].- Upstream Shopify#944's timeline has no events after 13:35 UTC, so nothing from this round.
Wording (potential availability)
contextual-objects.ts(doc block: "potentially", "This is not a guarantee", plus the layout example), theUndefinedObjectcomment (index.ts:236-242), theTypeSystem.tscomment (L366-372), the changeset (L8, L10), the PR #2 body ("Known limitation", rule heading) and the upstream body (L16, L18, L20) all describe potential availability and say only the definitions are shared. ThestartsWith/regex split is listed as a follow-up (upstream body L33).- The production code diff since
9272f1ais comments only. Behaviour is identical to round 3, so the round-3 Dawn 9/9 and Horizon 6/6 still hold. - Changeset:
minorfor theme-check-common (4 new public exports) andpatchfor theme-language-server-common. Correct.
Tests
| Claim | Measured |
|---|---|
3 specs on base sources (ee5ab68 fork / 20f8edf upstream) |
12 failed | 184 passed | 1 skipped on both |
| 3 specs on the branch | 196 passed | 1 skipped on both |
| Whole repo, fork, Node 20.19.2 | 268 files (+1 skipped), 7182 passed, 5 skipped, after removing the 6 codemirror-language-client/dist/esm/*.spec.js build copies. With them it is 7209 (= 7182 + 27). |
| Whole repo, upstream branch, Node 24.22.0 | 272 files (+1 skipped), 7192 passed, 5 skipped |
| Package counts | theme-check-common 121 files / 5368. theme-language-server-common 75 files / 655 + 1 skipped. These match the body. |
pnpm -r type-check |
exit 0 on both |
prettier (packages/*/src/**/*.ts, docs/fixes/**/*.mjs, changeset) |
clean on both |
- Mutation M1: theme-mode snippets/blocks set back to main's
['app','section','block']in bothindex.tsandTypeSystem.ts. 12 fail, including:- both nested inference tests and the nested completion test;
- the known-limitation test;
- the app-mode test's theme-contrast assertion.
- Mutation M2: app mode given the theme list. 3 fail: the app-mode inference, completion and check tests. So the app-mode negatives are real guards.
Repro and visual proof
- With matplotlib 3.11.2, 2 clean
capture.shruns were byte-identical to each other and to HEAD for all 8 artifacts (both PNGs, both TSVs, 4 txt). See nit 1 for a third run. theme-check-before.txthas 12 offenses and-after.txthas 5: 6 fixed,layout-rendered-search.liquid:9:21accepted, and 5 true positives remain.before-after.png(1247×686) andls-before-after.png(1464×1129) are both legible.- The amber "now accepted (known limitation)" row and the "before 12 / after 5" footer are visible.
- In the LS capture, C1-C7 and H1-H2 changed, and K1-K3 are unchanged.
- The repro theme has the layout-only snippet and the
{% render %}fromlayout/theme.liquid. - The test claims match the code: exact
['articles','products']/['title'],{kind:'array', valueType:'product'}→product→string, and app modeunknown/untyped/unknown. The#Lanchors at5ca4a59point to the right tests.
Bodies and hygiene
- Upstream body:
- The AI disclosure is still at L8.
- It says
Refs #944(not "Fixes"). - The hidden draft note correctly names
4ba504e+eace325on20f8edf. - Both "Drag in" slots point to existing PNGs at
5ca4a59.
- The PR #2 body uses code spans for upstream refs, so nothing new cross-links.
- No secrets, tokens or local paths in either diff, and no stray or log files. The upstream branch has no
docs/files.
Upstream branch eace325 is ready to open (squash merge, CLA signature needed).
…nd check all four are frozen
…ckout, docset wording in the PNG title
arman-dogru
left a comment
There was a problem hiding this comment.
Peer Reviewer: APPROVED (ccbb1bc)
Round 5. This checks the round-4 nit fixes. I used a fresh clone at /workspace/review/theme_tools_pr2_r5 and ran every git/gh command without GITHUB_TOKEN.
Result: 0 blocker, 0 major, 0 minor, 1 nit. All 4 round-4 nits are fixed and I checked each one, plus the unpinned link. All claimed numbers reproduce.
Findings
1. nit: capture.sh hides a crash in sort-vitest.mjs. At docs/fixes/944-predictive-search-snippets/capture.sh:39, the pipeline is npx vitest run … | strip | node $D/sort-vitest.mjs > $D/tests-$1.txt || true. The || true is there so the expected red run on base doesn't stop the script. But it also swallows a crash in the sort step, so the script exits 0 and leaves an empty or partial capture.
- Repro:
bash -c 'set -euo pipefail; printf "x\n" | node -e "process.exit(3)" > out.txt || true; echo continues'printscontinuesand leavesout.txtat 0 bytes. - Fix: write vitest's output to a temp file first (
… | strip > $tmp || true), then runnode $D/sort-vitest.mjs < $tmp > $D/tests-$1.txtwithout|| true. Optional, and it doesn't block.
Round-4 nits: verified fixed
- Capture order.
capture.shnow pipes the test output throughsort-vitest.mjs.- I ran
capture.sh3 times in a row (PYTHON = a matplotlib 3.11.2 venv). Every run exited 0 with 0 dirty files. All 8 artifacts were byte-identical across the 3 runs and to HEAD. - I also fed the sorter shuffled input. With the per-file summary blocks reversed, and separately with the FAIL detail blocks moved as whole-file groups, it reproduced the committed
tests-before.txtbyte for byte.
- I ran
- matplotlib pre-check (
capture.sh:18-21, before the dirty-tree check and any checkout).- With
python3lacking matplotlib, the script exits 1 with "python3 has no matplotlib", leaving 0 dirty files. - With
PYTHON=/nonexistent/python, it also exits 1 with 0 dirty files.
- With
- Check-side test (
index.spec.ts:408). It's renamed, pinned to literal arrays, and commented as "not a cross-package parity guard".- Mutations: removing
commentfromSECTION_CONTEXTUAL_OBJECTS→ 4 tests fail, including this one. Swappingappandblock→ only this test fails. - The PR body (L127, L134), the mermaid label (L57) and the upstream body L22 all name the
TypeSystemparity test as the guard.
- Mutations: removing
- Freeze test and wording.
- The freeze test (
index.spec.ts:463) covers all 4 lists, checkingisFrozenand thatpushthrowsTypeError. Unfreezing each list in turn fails this test and only this test, 4 of 4 times. - The
ls-before-after.pngtitle reads "Language server (docset built by pnpm build)", and it's legible. - The body link to
docs/upstream-944-pr-body.mdis pinned toccbb1bc.
- The freeze test (
History, authorship and upstream
- Fork PR #2: head is
ccbb1bca9681e60e399dac2410b51cdfb788c64b, and63afdb0is an ancestor. The new commits are344ba1c,2c78f08,a9e3927,d2faf9candccbb1bc. - Upstream branch: at
02052d7c6558de8fc2665f85ade33502c2a57377, andeace325is an ancestor.- Against Shopify main
20f8edf: ahead 3 / behind 0, 8 files, +503/−75. 344ba1cand02052d7have the same patch-id (67bd820…).- All 8 files are blob-identical to the fork head.
- The only file this round touches on the upstream branch is
index.spec.ts. There are nodocs/files on it.
- Against Shopify main
- Authorship: every new commit is authored and committed by
Arman Doğru <53595807+arman-dogru@users.noreply.github.com>. No commit message contains#(so no#944), and there are no Co-authored-by or tool trailers. - Upstream:
gh pr list -R Shopify/theme-tools --author arman-dogru --state allreturns[]. The last event on upstream Shopify#944 is still from 2026-10-21 13:35 UTC, so nothing new.
Numbers
| Claim | Measured |
|---|---|
3 specs on base sources (ee5ab68 fork / 20f8edf upstream) |
12 failed | 184 passed | 1 skipped on both |
| 3 specs on the branch | 196 passed | 1 skipped on both |
| Fork, whole repo, Node 20.19.2, no build output | 268 files (+1 skipped), 7182 passed, 5 skipped |
Fork after pnpm build |
274 files, 7209 passed, 5 skipped (+27) |
| Upstream branch, whole repo, Node 24.22.0, no build output | 272 files (+1 skipped), 7192 passed, 5 skipped |
Upstream branch after pnpm build |
278 files, 7219 passed, 5 skipped (+27) |
pnpm -r type-check |
exit 0 on both |
prettier (packages/*/src/**/*.ts, docs/fixes/**/*.mjs, changeset) |
clean on both |
Both bodies explain the 7182 vs 7192 difference (main added 4 spec files and 10 tests after ee5ab68), and that matches the figures above.
Links and bodies
- I extracted every URL from both bodies: 37 unique, all HTTP 200. 36 of them are fork URLs pinned to a full SHA (
d2faf9corccbb1bc). Developer said 34; the difference is only in how they're counted. - The
#Lanchors atd2faf9cland on the named tests (L365, 386, 396, 408, 428, 445, 463, 476). - The upstream body still has:
- the AI disclosure at L10;
Refs #944;- a draft note listing all 3 commits;
- both "Drag in" slots pinned to
d2faf9c.
- No secrets or local paths in the diff, and no noise files.
Upstream branch 02052d7 is ready for Arman to open (upstream squash-merges; CLA signature needed).
…he captures on any failure
arman-dogru
left a comment
There was a problem hiding this comment.
Peer Reviewer, round 6 at 6ef1006: APPROVED (0 blocker, 0 major, 0 minor, 0 nit)
These two commits (e0da659, 6ef1006) fix the round-5 nit, where capture.sh:39 used || true and so also hid a sort-vitest.mjs crash. Only docs/fixes files changed (capture.sh +18/-2, README +1/-1). Production code is untouched since ccbb1bc.
What I ran (Node 20, fresh run of the script on my machine):
- Clean run at 6ef1006: exit 0, and
git statusis empty afterwards. So all regenerated captures, including both PNGs, are byte-identical to the committed ones. - Failure injection: I made a local, never-pushed commit that adds
process.exit(3)to the top ofsort-vitest.mjs, then ran the script. It exits 3, printscapture.sh: failed (exit 3); restored docs/fixes/944-predictive-search-snippets to HEAD, leaves no modified or untracked files, and restores the check/TypeSystem sources. - The script still runs with
set -euo pipefail, so astripfailure in the pipe also aborts the run now. Only vitest's own exit status is ignored, and the README row says exactly that. - Both commits are authored as Arman Doğru with the noreply address.
Not checked this round: the box-only upstream branch, which these docs-only commits don't touch as far as I can tell. Nothing blocks the upstream submission from my side.
Partly addresses upstream
Shopify/theme-toolsissue944. This is a fork-only PR. Upstream references here are code spans on purpose, so the fork doesn't cross-reference upstream.Not a full fix of
944: the thread also asks forformandpaginate, and those stay reported deliberately (see "Still reported"). An upstream PR would say "Refs", not "Fixes".Why this issue (and not
608or722)608is already fixed on main. Upstream00b683e(2024-11-26) replaced the check that flaggedsettings[].typeunderblocks[]. The screenshot's schema gives 0 offenses on main.722's fix lives in Shopify/theme-liquid-docs.section.jsondeclareslocalesvalues as strings only.944was half done. Upstreamd4ec896(PR1267) allowedsection/blockin theme snippets in the check only. The language server was left behind, andpredictive_search/recommendationswere still reported.The rule (one rule for every section object; potential availability, not a guarantee)
Shopify's
renderdocs say a rendered snippet can access "any objects that are directly accessible outside the snippet … a snippet … inside a section can access thesectionobject". A theme block is rendered inside a section, and a snippet is usually rendered from a section or block. So both are treated as potentially able to read everything a section can read, plusappandblock:ee5ab68sections/*blocks/*snippets/*(check: only with{% doc %})blocks/*,snippets/*in app modepredictive_searchandrecommendationscarry the same shopify.dev restriction: results only when the section is rendered through their API plus the Section Rendering API. They are now treated identically, which settles the earlier open question.commentis in the section list because, once anew_commentform is posted,commentbecomes globally available (upstreamec1fbd7,352). Horizon'ssnippets/blog-comment-form.liquidreadscomment.statusinside itsnew_commentform, and the repro now covers it. The lists are frozen (Object.freeze), defined once incontextual-objects.ts, which explains why, and are used byUndefinedObjectandTypeSystem.Known limitation (Arman's P2):
layout/theme.liquiddoing{% render 'search-results' %}. There,predictive_searchisn't defined, but the snippet is still accepted.sectionandblockin snippets (upstreamd4ec896).snippets/layout-rendered-search.liquid) and a test pin this case.Only the definitions are shared (Arman's P3):
relativePath.startsWith(...)in the check, path regexes inTypeSystem. So the two can still disagree on unusual paths.Still reported, on purpose:
form,paginate(and other tag-scoped variables):renderuses an isolated subcontext that doesn't carry them, which is why Dawn passespaginate: paginateexplicitly.The language server isn't doc-gated.
UndefinedObjectskips snippets without{% doc %}.TypeSystemtypes the contextual objects in every theme-mode snippet, with or without doc tags.flowchart TD L["theme-check-common/contextual-objects.ts<br/>SECTION_CONTEXTUAL_OBJECTS<br/>THEME_BLOCK_AND_SNIPPET_CONTEXTUAL_OBJECTS = app, block + section objects<br/>APP_BLOCK_CONTEXTUAL_OBJECTS, CHECKOUT_LAYOUT_CONTEXTUAL_OBJECTS"] subgraph check["UndefinedObject (theme-check-common)"] A["create()"] --> B{"snippets/* without {% doc %}?"} B -- yes --> Z["skip file"] B -- no --> C["collect lookups and scopes"] --> D["onCodePathEnd → globalObjects"] --> E["getContextualObjects(path, mode)"] E --> G["isDefined? else 'Unknown object'"] end subgraph ls["TypeSystem (theme-language-server-common), no doc gate"] T["getContextualEntries(uri, mode)"] --> H["inferType / completion / hover"] end L --> E L --> T P["TypeSystem parity test types every entry of the shared lists"] -.-> L N["file classification NOT shared:<br/>startsWith (check) vs regexes (TypeSystem)"] -.- E N -.- T style L fill:#fde2e2,stroke:#b22222Evidence: check
repro-theme/has:blog-comment-formsnippet (modelled on Horizon's) that readscomment.statusinside{% form 'new_comment' %};layout/theme.liquid(the known limitation: before, it was reported; after, it's accepted);Only
UndefinedObjectis enabled.capture.shdoes the following:$PYTHONhas no matplotlib or the tree is dirty.ee5ab68's check andTypeSystemsources (a trap restores them).sort-vitest.mjs, which orders the per-file blocks by file. Without it, vitest prints files in completion order, which changed in 1 of the reviewer's 3 runs.sort-vitest.mjsruns on that file without|| true. On any failure the trap restores the captures directory to HEAD.With a forced
sort-vitest.mjscrash (aNODE_OPTIONS=--requirepreload that throws only in that script; no tracked file changed),capture.shexited 1, the tree stayed clean, and all 8 artifacts matched HEAD. Two normal runs after that exited 0 and were byte-identical to HEAD.I ran it three times in a row on a clean tree: all 8 artifacts were byte-identical across the runs, and the tree stayed clean. Lines are sorted by file, line and column (
offenses.mjs).blocks/predictive-search-terms.liquid:2:7predictive_searchsnippets/predictive-search-results.liquid:7:25and:8:22predictive_searchsnippets/blog-comment-form.liquid:8:10commentsnippets/recommendation-list.liquid:6:23and:7:20recommendationssnippets/layout-rendered-search.liquid:9:21predictive_search (rendered only from the layout)layout/theme.liquid:8:8predictive_searchtemplates/search.liquid:2:4recommendationssnippets/true-positives.liquid:8:4form,:9:4paginate,:10:4undefined_thingRendered by
render_capture.pyfrom the two capture files.Changeset: minor for
@shopify/theme-check-common(4 new public exports), patch for@shopify/theme-language-server-common.Real themes, all checks, before → after with identical lines: Dawn 16.0.0: 9 → 9; Horizon
dfad90d: 6 → 6.Evidence: language server
ls-capture.mjsdrives the builtCompletionsProviderandHoverProviderwith the docset thatpnpm buildwrites topackages/theme-check-docs-updater/data(revision40f966fis recorded in the TSVs; no cache, no network). The probe snippetsnippets/s.liquidhas no doc tag. Data: before / after.snippets/s.liquid{{ pre█ }}{{ predictive_search.█ }}{{ recommendations.█ }}{{ section.█ }}{% for p in predictive_search.resources.products %}{{ p.ti█ }}blocks/b.liquid{{ predictive_search.█ }}{% form 'new_comment', article %}{{ comment.█ }}predictive_searchin a snippet### predictive_searchdocscommentin a snippet### commentdocstemplates/search.liquid{{ predictive_search.█ }}{{ pre█ }}recommendationsin a blockTests
Check (
index.spec.ts):predictive_searchin a doc'd snippet, and still reported in templates and layouts.section, predictive_search, recommendations, comment. This replaces upstream's "still flag block-only objects" test.TypeSystemparity block below.commentwith anew_commentform.TypeSystem:
it.eachover the shared lists, so it fails if the check's lists and the LS diverge. Its other two tests use literal arrays.predictive_search.resources.productsin a doc-tagged snippet infers{ kind: 'array', valueType: 'product' };product, andp.titleinfersstring;predictive_searchandcommentareunknown, and the nested lookup isuntyped. Theme mode is asserted to differ.ObjectCompletionProvider:
predictive_search.resources.█completes exactly['articles', 'products'], andp.█in the loop completes['title'];predictive_search,commentand the nested lookup all complete[].d4ec896changed only the check and left the LS inconsistent with it.3 touched spec files (before / after):
ee5ab68check + TypeSystem sources, branch testsThe app-mode negatives fall into two groups. The completion test and the app-mode inference assertions pass on base too: they guard against regressions. The app-mode inference test also asserts that theme mode is typed, and that assertion is the part that fails on base.
Full suites (
CI=true vitest run, CI matrix Node 22 and 24, plus Node 20):pnpm build(Node 22)dist/esmspec copiespnpm -r type-check(all packages)prettier --check "packages/*/src/**/*.ts"anddocs/fixes/**/*.mjsAbout the review's "7195 passed": that clone had run
pnpm build, which emits 6 compiled spec copies underpackages/codemirror-language-client/dist/esm/. Vitest collects them: 275 files and 27 extra tests. 7195 − 27 = 7168, the source-only figure reported at bb4a84c. Later heads add tests: this one has 7182 on a tree without build output.7182 here vs 7192 on the upstream branch: that branch is based on upstream main
20f8edf, notee5ab68. Main has since added 4 spec files and 10 tests, giving 272 files and 7192, or 7219 afterpnpm build(+27, measured). The fork has no CI runs, because Actions workflows aren't enabled on it.Upstream-ready variant (prepared, not opened)
Branch
upstream-944-section-objects-in-theme-snippets, pushed to the fork as a plain branch (no PR):4ba504e+eace325(Arman's review fixes) +02052d7(round-4 test nits), each added on top without force-push (upstream squash-merges), on upstream main20f8edf;docs/fixes/, and no red commit.02052d7); 3 touched specs 196 passed + 1 skipped;pnpm -r type-checkand prettier clean.The PR body, in upstream's template, is in
docs/upstream-944-pr-body.md. Arman opens it himself.Commits on this PR
dfb8f3ffailing tests4305404first fixb712783,bb4a84cevidenceeb5292areview: one rule, shared lists, parity tests, theme blocksb8b8bfa,86570f1,1584359review: LS capture, capture.sh hardening, regenerated evidencecommentrationale/test/repro/probes, docset frompnpm build, K3 control, wrapped PNG cells, then regenerated evidenceb2efdc4: prepared upstream PR body (docs/upstream-944-pr-body.md)11c5042,0373749,5ca4a59,63afdb0: Arman's review (potential availability and known limitation, shared definitions only, nested and app-mode tests, literal expectations), then regenerated evidenceSupersedes fork PR #1 (
1204).344ba1c,2c78f08,a9e3927,d2faf9c,ccbb1bc: round-4 nits (literal list pin and all-four freeze test, sorted test captures, matplotlib pre-check, PNG title), then regenerated evidencee0da659,6ef1006: round-5 nit (capture.sh no longer hides asort-vitest.mjscrash; it restores the captures on failure)