Skip to content

feat(content-drive): browse scopes for All, site root and System Host (#37426) - #37487

Draft
zJaaal wants to merge 10 commits into
mainfrom
37426-content-drive-browse-scopes
Draft

feat(content-drive): browse scopes for All, site root and System Host (#37426)#37487
zJaaal wants to merge 10 commits into
mainfrom
37426-content-drive-browse-scopes

Conversation

@zJaaal

@zJaaal zJaaal commented Sep 9, 2026

Copy link
Copy Markdown
Member

Proposed Changes

Content Drive can express one browse scope today: the whole current site, at every depth, with System Host mixed in. Browsing "the site" and browsing "the site root" are the same request, and System Host cannot be reached on its own. This PR splits that into three browse scopes picked from the sidebar. Spelled in full throughout: Content Drive is separately gaining a search scope (#37479) that says which fields a search reads, and bare "scope" would name either.

  • All, above the hierarchy — the whole site at any depth, content only, and read-only: nothing can be uploaded, created, or dropped there.
  • The site row, in the hierarchy — now means the site root: what sits at the top of the site, plus its top-level folders. Folders below it are unchanged.
  • System Host, below the hierarchy — shared content on its own, and a drop target, so content can be moved there by dragging.
  • The Show Shared Assets chip becomes Show System Host, and is offered only while All is selected.
  • One URL value says where the drive is browsing, rather than a scope beside a path that could disagree with it: absent means All, / means the site root, a deeper path means that folder, and a reserved word means System Host. Links made before this change keep meaning what they meant.
  • Backend: the listing gains a browse scope, and a request that carries none behaves exactly as it does today. That is what leaves the Asset Picker and the other consumers of the shared listing untouched.

The spec lands first, in specs/37426-content-drive-browse-scopes/spec.md; the implementation follows in this same PR. Draft until both are in.

The contract delta is worth reading on its own if you own a caller of POST /v1/drive/search: contracts/drive-search-browse-scope.md. Short version: omitting the new field returns what it returns today, byte for byte, and the field is deliberately not defaulted, because defaulting it would silently turn the Asset Picker's folder requests recursive.

Checklist

  • Tests
  • Translations
  • Security Implications Contemplated (add notes if applicable)

Additional Info

Reviewers: the spec is the thing to read now. The code will be written against it, so a disagreement is much cheaper to settle here than after the implementation.

Fixes #37426

🤖 Generated with Claude Code

… and System Host

Content Drive has one browse scope today: the whole current site at any depth,
with System Host mixed in. Selecting the site and browsing the site root are the
same request, and System Host cannot be reached on its own.

Specifies three scopes picked from the sidebar. All (the whole site, content
only, read-only) sits above the site hierarchy, whose site row now means the
site root. System Host sits below it, listing shared content on its own and
accepting content dropped onto it. The "Show Shared Assets" chip becomes "Show
System Host" and applies only to All.

Callers that ask for no scope keep today's behavior, which is what leaves the
Asset Picker and the other consumers of the shared listing untouched.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Claude finished @zJaaal's task in 1m 23s —— View job


Review — Content Drive browse scopes (spec-only PR)

  • Read the three changed files (spec.md, data-model.md, contracts/drive-search-browse-scope.md)
  • Verified the concrete code references the spec makes against the repo
  • Checked internal consistency across the three documents

This PR adds spec/design docs only — no production code — so there are no runtime bugs to find. I focused on whether the checkable claims in the docs are actually true against the codebase, since a spec that cites the wrong seam sends the implementation phase the wrong way.

New Issues

  • 🟡 Medium: specs/37426-content-drive-browse-scopes/contracts/drive-search-browse-scope.md:52 — The refusal section cites BulkUploadForm.isExactlyOneTargetGiven as the precedent to follow, complete with a quoted rationale. No such class or method exists in the repogrep -rn "BulkUploadForm\|isExactlyOneTargetGiven" returns only this spec file. Either the reference is misremembered (there may be a real "both-targets-given → refuse" validator under a different name) or invented. It should point at a real symbol/path or be reworded as a general principle, otherwise the plan/implement phase will chase a nonexistent precedent. Fix this →

Verified accurate (worth recording)

These claims all check out against the code, so the design rests on solid ground:

  • data-model.md:53 — the //demo.dotcms.comSYSTEM_HOST hazard is real: dot-content-drive.store.ts:129 interpolates `//${currentSite()?.hostname}${path() || '/'}`, so a SYSTEM_HOST path token would concatenate with no separating slash. The call for explicit mapping over template interpolation is justified.
  • data-model.md:26,36forceSystemHost boolean exists on BrowserQuery (BrowserQuery.java:73), and appendSystemHostQuery exists (BrowserAPIImpl.java:2208, called at :2040). The SystemHostMode replacement and the EXCLUDE-as-default back-compat argument map onto the real code.
  • ✅ Internal consistency across the three docs holds: "absent = today's behavior", "explicit scope + folder path = 400", includeSystemHost read only for ALL/absent, and the single-URL-value model are stated consistently in all three.

Note (non-blocking)

The two prior reviewer gaps (query-path parity per heuristic; per-consumer back-compat enumeration) are addressed by FR-012, FR-028, SC-004 and SC-007. Those are now spec requirements; whether the enumeration is complete is a plan/tasks-phase concern, not something checkable here.

Net: one doc reference to correct before the spec is trusted as the implementation contract. Not a blocker for the spec's design, which is sound.
· branch 37426-content-drive-browse-scopes

All is the default only on a cold open. A URL that names a scope opens on that
scope; one that names none opens the folder it points at, or All when it points
nowhere. That keeps links made before this feature meaning what they meant, and
it makes the scope behave like every other filter rather than like a preference
the URL cannot override.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ihoffmann-dot

Copy link
Copy Markdown
Member

The spec looks solid overall — two acceptance-criteria gaps I'd flag before signing off:

1. Query builder parity has no dedicated test

The driving issue notes buildPureESQuery and the SQL path disagree on how System Host is handled, and today that only bites under the PURE_ES heuristic (not the default). FR-012 and SC-004 cover this only implicitly, through a general "same results with/without search" criterion.

Given this is a real, already-identified bug, I'd want an explicit acceptance scenario that exercises both query heuristics (HYBRID_SINGLE_CHUNKED_QUERY_ES and PURE_ES) for the ROOT and SYSTEM_HOST scopes specifically, so it can't silently regress.

2. No-scope backward compatibility isn't broken down by consumer

SC-007 says "surfaces other than Content Drive keep listing exactly what they list today," but there are 6 named consumers of the shared listing:

  • Asset Picker
  • Assets API
  • The old file browser
  • The deprecated tree endpoint
  • The legacy admin browser
  • The Velocity viewtool
  • 2 internal callers

One aggregate success criterion doesn't tell us which of those get an actual regression test vs. which are assumed safe by inspection. Could the plan/tasks phase enumerate this explicitly?

One URL value says where the drive is browsing, rather than a scope beside a
path that could disagree with it: absent means All, `/` means the site root, a
deeper path means that folder, and a reserved word means System Host. Real
folder paths all begin with `/`, so a reserved word can never collide with one,
and links made before this feature keep meaning what they meant.

The search rule was worded backwards. It said a selection returns the same
items with or without a search, which reads as "search does nothing" and
contradicts the success criterion that already allows for what a search
excludes. It now says filtering may only remove items from the scope, never add
items from outside it, whichever internal search path serves the request, and
both paths are exercised for the site root and System Host rather than only the
one that runs by default.

Saying All and System Host have none of the behaviors of folders was too broad,
since System Host does take content. Narrowed to structure.

Backward compatibility is no longer one blanket claim over eight consumers: the
shared seam they all reach gets a test, and each consumer is recorded as covered
by it or checked by inspection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zJaaal

zJaaal commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Both added in 6b40cd126a. Thanks, the second one was a blanket claim that deserved to be broken up.

Query-path parity. You are right that it was only implied. Two things changed. The requirement it hung off was actually worded backwards: it said a selection returns the same items with or without a search, which reads as "search does nothing" and contradicts the success criterion that already allows for what a search legitimately excludes. It now says a search or field filter may only remove items from the selected scope and never add items from outside it, whichever internal search path serves the request. On top of that there are now explicit acceptance scenarios for the site-root and System Host scopes stating both paths return the same items, and the Legacy section spells out that only one of the two runs by default, so proving they agree means deliberately exercising each rather than waiting for the non-default one to turn up. Concretely that is flipping BROWSE_API_HEURISTIC_TYPE between HYBRID_SINGLE_CHUNKED_QUERY_ES and PURE_ES in the test, no production change.

Per-consumer backward compatibility. Added as a requirement rather than left to the plan, since it is the promise and not just how we check it: the claim must be demonstrated rather than assumed, and every consumer must be recorded as covered by a test or checked by inspection, with none unaccounted for. The success criterion now says the same.

Worth knowing how small that turns out to be in practice. All eight of those callers reach the listing through one builder, and none of them sets the System Host flag or the folder-skip flag today. So the entire risk collapses to a single question: does the new three-state default to the same host clause the boolean produces today? One test pinning the default at that shared seam covers every consumer at once, and the rest of the list is inspection. The plan will carry the enumeration so it is visible rather than assumed.

Reply written by Claude on @zJaaal's behalf.

@zJaaal

zJaaal commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

All three addressed in 6b40cd126a.

The search requirement was worded backwards. Correct, and it was the one worth catching. It now says a search or field filter may only remove items from the selected scope and never add items from outside it, which is the invariant that was meant, and it adds that the answer must not depend on which internal search path served the request.

"None of the behaviors that belong to folders" was too absolute. Also correct. Narrowed to structure: no expansion control, no children, nothing beneath them to navigate into, with an explicit note that this does not stop System Host accepting content.

The site-row scope had no URL representation. This was the real gap, and the fix went further than the suggestion. Rather than adding a scope parameter beside the existing location, the spec now requires a single value to say where the drive is browsing, because two values can disagree and then neither is the answer. Absent means All, / means the site root, a deeper path means that folder, and a reserved word means System Host. Every real folder path begins with / and no reserved word does, so the two can never collide. This also drops the earlier precedence rule for a URL that named a scope and carried a folder, which was only needed because the two could contradict each other.

Reply written by Claude on @zJaaal's behalf.

ihoffmann-dot
ihoffmann-dot previously approved these changes Sep 11, 2026
zJaaal and others added 2 commits September 11, 2026 16:48
Content Drive is separately gaining a search scope (#37479), which says which
fields a search reads. This one says where you are browsing. Two different
things, and "scope" on its own would name either, so every mention here is now
spelled in full and the Key Entities entry says what the other one is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ AI review failed

Claude did not produce a review — the backend call errored before generating any output (provider: anthropic-bedrock, model: global.anthropic.claude-sonnet-5). This usually means the model has no Bedrock access grant in the target account, or the model ID is invalid — not a problem with this PR.

Run: #34640908222

@github-actions

Copy link
Copy Markdown
Contributor

⚠️ AI review failed

Claude did not produce a review — the backend call errored before generating any output (provider: anthropic-bedrock, model: global.anthropic.claude-sonnet-5). This usually means the model has no Bedrock access grant in the target account, or the model ID is invalid — not a problem with this PR.

Run: #34640908195

zJaaal and others added 3 commits September 11, 2026 17:15
Two callers share POST /v1/drive/search, and one of them is owned elsewhere, so
what the new field means when it is absent is the whole promise. Written down
rather than left to the implementation: omitting the browse scope returns what
it returns today, byte for byte, and the field is deliberately NOT defaulted to
ALL, because ALL as unconditional folder-skipping would silently turn the Asset
Picker's folder requests recursive.

An explicit scope is valid only at the site root. A folder path with any scope
is refused rather than resolved by precedence, following the bulk upload form:
picking one of two contradictory statements puts content somewhere the caller
did not choose.

plan.md, research.md and quickstart.md stay local per .gitignore:228.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[TASK] Content Drive: browse scopes for All, site root and System Host

2 participants