Skip to content

feat(tree): add stagedOptionalRecursive#27042

Open
noencke wants to merge 30 commits intomicrosoft:mainfrom
noencke:noencke/staged-optional-recursive
Open

feat(tree): add stagedOptionalRecursive#27042
noencke wants to merge 30 commits intomicrosoft:mainfrom
noencke:noencke/staged-optional-recursive

Conversation

@noencke
Copy link
Copy Markdown
Contributor

@noencke noencke commented Apr 14, 2026

Description

Follow-up to #26918 (stagedOptional). Adds recursive versions of the methods.

Changes:

stagedOptionalRecursive — Adds the recursive-type variant of stagedOptional on SchemaStaticsAlpha and SchemaFactoryAlpha (instance + static). Follows the same pattern as optionalRecursive/requiredRecursive/withDefaultRecursive: relaxes type constraints via ImplicitAllowedTypesUnsafe and returns FieldSchemaAlphaUnsafe. The implementation simply casts the non-recursive stagedOptional function, matching the withDefaultRecursive pattern.

Reviewer Guidance

The review process is outlined on this wiki page.

noencke and others added 23 commits April 1, 2026 19:52
…uired→optional field migration

Adds `SchemaFactoryAlpha.stagedOptional(T)` to enable migrating a required field to optional
across multiple deployments without breaking cross-version collaboration.

- Required field stays in the stored schema during the rollout period, so old clients are unaffected
- New clients using `stagedOptional` see the field as Optional in the view (TypeScript type is `T | undefined`)
- Writing `undefined` is blocked at runtime until the stored schema is upgraded to Optional
- Migrating to `sf.optional(T)` upgrades the stored schema and enables clearing the field

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…clarify docs

- filterFieldAllowedTypes now preserves the isOptionalStaged SchemaUpgrade marker
  when using the Unchanged (view-copy) path, so getSimpleSchema output retains staged
  optional information.
- Clarify SimpleFieldSchema.isOptionalStaged doc comment: false = view, not staged;
  absent = derived from stored schema.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ce ambiguity

SchemaFactoryAlpha.stagedOptional has both a static and instance declaration,
making it ambiguous for TSDoc links. Link to SchemaStaticsAlpha.stagedOptional
instead, which is unambiguous (interface member).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…dering

SchemaFactoryAlpha.stagedOptional (instance and static) landed with
"stagedOptionalUpgrade" | "defaultProvider" while SchemaStaticsAlpha had
"defaultProvider" | "stagedOptionalUpgrade". CI compiles fresh TypeScript and
produces alphabetical order for both. Align the class members to match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Optional to includeStaged

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Rename isOptionalStaged → isStagedOptional to match the stagedOptional factory naming
- Fix comment capitalization in discrepancies.ts (optional/required not capitalized)
- Rename describe block to "staged allowed type upgrade" (was "staged schema upgrade")
- Expand JSDoc on isStagedOptional to explain why false and undefined are distinct

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
undefined for isStagedOptional only means "stored schema" — view schemas
are always constructed by the current in-memory package, so there is no
"legacy view field" scenario.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
- Remove inline code backticks from changeset title per repo guidelines
- Clarify changeset description to lead with required→optional migration
- Fix JSDoc in schemaFactoryAlpha.ts step 3 wording (Josh's suggestion)
- Reword isStagedOptional JSDoc to "Indicates that..." instead of reading as an action

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Aligns with how AllowedTypeMetadata.stagedSchemaUpgrade is documented —
no restriction on setting the property by hand.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Swap Omit union member order from "stagedOptionalUpgrade" | "defaultProvider"
to "defaultProvider" | "stagedOptionalUpgrade" to match CI's api-extractor output.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The @System tag was removed from the source code but the API report files
were not regenerated to match.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The previous ordering fix incorrectly changed all Omit union orderings to
"defaultProvider" | "stagedOptionalUpgrade". CI generates different orderings
for class members vs interface members:
- SchemaFactoryAlpha (class): "stagedOptionalUpgrade" | "defaultProvider"
- SchemaStaticsAlpha (interface): "defaultProvider" | "stagedOptionalUpgrade"

Reverts the class member ordering to match what CI actually produces, verified
against the last passing CI build.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add SchemaFactoryAlpha.stagedOptionalRecursive for recursive schema
  support, following the same pattern as optionalRecursive/requiredRecursive
  (relaxed type constraints via ImplicitAllowedTypesUnsafe)
- Fix stagedOptional JSDoc to accurately describe the enforcement
  mechanism: stored-schema validation, not a dedicated runtime guard
- Update changeset description to match
- Update alpha API report

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@noencke noencke requested review from a team as code owners April 14, 2026 21:47
Copilot AI review requested due to automatic review settings April 14, 2026 21:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds a “staged optional” field mode to support incremental required→optional migrations and introduces the recursive-typing variant, plus updates schema conversion and compatibility logic to recognize staged optional fields.

Changes:

  • Add SchemaFactoryAlpha.stagedOptional and SchemaFactoryAlpha.stagedOptionalRecursive (and corresponding statics) and plumb staged-optional metadata through schemas.
  • Extend stored-schema generation to optionally treat staged-optional fields as optional via includeStagedOptional.
  • Add/adjust tests and API reports/changesets to reflect the new alpha API surface and behavior.

Reviewed changes

Copilot reviewed 14 out of 14 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts Adds stagedOptional/stagedOptionalRecursive APIs and implementation.
packages/dds/tree/src/simple-tree/core/toStored.ts Extends stored-from-view generation options with includeStagedOptional.
packages/dds/tree/src/simple-tree/toStoredSchema.ts Implements staged-optional kind selection for stored schema generation.
packages/dds/tree/src/simple-tree/simpleSchema.ts Adds isStagedOptional to SimpleFieldSchema with detailed semantics.
packages/dds/tree/src/simple-tree/fieldSchema.ts Adds stagedOptionalUpgrade props and exposes isStagedOptional getter.
packages/dds/tree/src/simple-tree/api/discrepancies.ts Suppresses kind discrepancies for staged-optional vs required stored during rollout.
packages/dds/tree/src/simple-tree/api/storedSchema.ts Wires includeStagedOptional when extracting persisted schema.
packages/dds/tree/src/test/simple-tree/api/stagedSchemaUpgrade.spec.ts Adds staged-optional upgrade tests and renames existing describe.
packages/dds/tree/src/test/simple-tree/toStoredSchema.spec.ts Updates callers for new includeStagedOptional option.
packages/dds/tree/src/test/testTrees.ts Updates stored-schema generation options in test documents.
packages/dds/tree/api-report/tree.alpha.api.md API report updates for new staged optional APIs/fields.
packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md API report updates for staged optional fields and extractor churn.
.changeset/fruity-cars-tell.md Changeset for stagedOptional.
.changeset/brave-owls-jump.md Changeset for stagedOptionalRecursive.
Comments suppressed due to low confidence (1)

packages/dds/tree/src/simple-tree/toStoredSchema.ts:1

  • The current isStagedOptional preservation logic drops the explicit false value (it only preserves values that are instanceof SchemaUpgrade). This loses the documented distinction between false (“view schema field, not staged optional”) and undefined (“stored schema field”), which can cause downstream logic that relies on !== false checks (e.g., discrepancy suppression) to misclassify non-staged fields as staged optional. Preserve false as well when preservesViewData(options) is true (e.g., by passing through f.isStagedOptional instead of filtering to only SchemaUpgrade instances).
/*!

Comment thread packages/dds/tree/src/simple-tree/api/discrepancies.ts
Comment thread packages/dds/tree/src/simple-tree/api/storedSchema.ts
Comment thread packages/dds/tree/src/simple-tree/toStoredSchema.ts
noencke and others added 2 commits April 14, 2026 22:08
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@noencke noencke requested a review from a team as a code owner April 14, 2026 22:17
noencke and others added 2 commits April 14, 2026 22:54
Reverts the JSDoc edit to the original fruity-cars-tell changeset.
Regenerates all API reports from a clean build to eliminate
non-deterministic union member reordering from incremental builds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…churn

- Type-level assignability test in schemaFactoryRecursive.spec.ts
  (parallels optionalRecursive tests)
- Runtime test in stagedSchemaUpgrade.spec.ts: recursive schema
  with stagedOptionalRecursive, ValidateRecursiveSchema, and
  unhydrated node construction
- Revert beta/legacy API reports to base branch versions to avoid
  upstream merge churn showing in the PR diff

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@noencke noencke force-pushed the noencke/staged-optional-recursive branch from 199507d to 8d1de83 Compare April 14, 2026 23:06
Comment thread packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
@noencke noencke force-pushed the noencke/staged-optional-recursive branch from 8d1de83 to a49544c Compare April 14, 2026 23:21
@noencke noencke changed the title feat(tree): add stagedOptionalRecursive and fix stagedOptional JSDoc feat(tree): add stagedOptionalRecursive Apr 15, 2026
Comment thread .changeset/brave-owls-jump.md Outdated
Comment thread .changeset/brave-owls-jump.md Outdated
Comment thread packages/dds/tree/src/simple-tree/api/schemaFactoryAlpha.ts
noencke and others added 2 commits April 15, 2026 17:25
Merge brave-owls-jump.md into fruity-cars-tell.md since both stagedOptional
and stagedOptionalRecursive will ship in the same release. Add usage examples
for both the standard and recursive variants. Remove unnecessary blank lines
in the schemaStaticsAlpha object.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@noencke noencke force-pushed the noencke/staged-optional-recursive branch from a49544c to 316b3ae Compare April 15, 2026 17:52
These were incorrectly modified by earlier commits on this branch.
This PR has no beta-level API changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  279037 links
    1880 destination URLs
    2126 URLs ignored
       0 warnings
       0 errors


1 similar comment
@github-actions
Copy link
Copy Markdown
Contributor

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  279037 links
    1880 destination URLs
    2126 URLs ignored
       0 warnings
       0 errors


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants