Skip to content

refactor!: move base extension contracts to abstractions#1983

Open
yuto-trd wants to merge 2 commits into
mainfrom
yuto-trd/thin-extensibility-boundary
Open

refactor!: move base extension contracts to abstractions#1983
yuto-trd wants to merge 2 commits into
mainfrom
yuto-trd/thin-extensibility-boundary

Conversation

@yuto-trd

@yuto-trd yuto-trd commented Jun 21, 2026

Copy link
Copy Markdown
Member

Description

  • Extract Extension, ExtensionSettings, and ExportAttribute into the new Beutl.Extensibility.Abstractions project while preserving the Beutl.Extensibility namespace.
  • Keep UI/media/property-editor extension contracts in Beutl.Extensibility; Beutl.Extensibility now references the abstraction project.
  • Move extension settings change-handler bookkeeping into PackageManager so host persistence state is not part of the root extension contract.
  • Recognize Beutl.Extensibility.Abstractions dependencies when reading local package target versions.

Affected areas

  • Beutl.Engine (rendering / scene / track)
  • Beutl.ProjectSystem (project / document persistence)
  • UI (Beutl.Editor, Beutl.Editor.Components, Beutl.Controls)
  • Beutl.Extensibility (plugin abstractions)
  • Beutl.NodeGraph (node editor)
  • Beutl.FFmpegIpc / Beutl.FFmpegWorker (media IPC boundary)
  • Beutl.Api (server API client)
  • Build / CI / docs only

Breaking changes

Extension, ExtensionSettings, and ExportAttribute moved from the Beutl.Extensibility assembly to Beutl.Extensibility.Abstractions while keeping the Beutl.Extensibility namespace. Downstream plugins that reference these root contracts must reference the new abstraction package/assembly or rebuild against packages that transitively include it.

Test plan

  • dotnet test tests\Beutl.Extensibility.Abstractions.Tests\Beutl.Extensibility.Abstractions.Tests.csproj -f net10.0 --no-restore — passed, 3 tests.
  • dotnet test tests\Beutl.UnitTests\Beutl.UnitTests.csproj -f net10.0 --no-build --filter "FullyQualifiedName~Beutl.UnitTests.Api.LocalPackageTests" — passed, 3 tests.
  • dotnet build Beutl.slnx --no-restore — passed with existing nullable/NU190x warnings.
  • dotnet format Beutl.slnx --include <changed files> --verify-no-changes — passed.
  • Full dotnet test tests\Beutl.UnitTests\Beutl.UnitTests.csproj -f net10.0 --no-build was run and failed in 3 AutoSaveServiceTests (SaveError_Subscription_ShouldReceiveErrors, SaveObjects_WhenExceptionOccurs_ShouldContinueWithNextObject, SaveObjects_WithNonExistentPath_ShouldEmitSaveError). The same 3-test filter also fails on unchanged main, so this is treated as a pre-existing local baseline failure rather than a regression from this PR.

Fixed issues / References

  • Project board: b-editor/projects/9 ("設計改善: 拡張 API の境界を薄くする")

Summary by CodeRabbit

  • New Features
    • Added Beutl.Extensibility.Abstractions as a lightweight abstractions-only extensibility package, included in the solution/build and supported by SDK auto-referencing with a dedicated opt-out property.
  • Bug Fixes
    • Improved package version selection to fall back to the abstractions layer when needed (case-insensitive).
    • Updated runtime dependency mapping so abstractions-origin assets resolve consistently.
  • Documentation
    • Expanded the SDK README with “thin (abstractions-only) plugin” guidance and per-package opt-out instructions.
  • Tests
    • Added unit and assembly boundary tests for the new abstractions contracts.

@coderabbitai

coderabbitai Bot commented Jun 21, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 88491d32-89c3-4fac-9c33-35223ccc1062

📥 Commits

Reviewing files that changed from the base of the PR and between ed906be and 94e8634.

📒 Files selected for processing (6)
  • sdk/Beutl.Extensibility.Sdk/Sdk/Sdk.targets
  • sdk/README.md
  • src/Beutl.Api/Services/LocalPackage.cs
  • src/Beutl.Api/Services/PackageManager.cs
  • tests/Beutl.Extensibility.Abstractions.Tests/ExtensibilityAbstractionsAssemblyTests.cs
  • tests/Beutl.UnitTests/Api/LocalPackageTests.cs
✅ Files skipped from review due to trivial changes (1)
  • sdk/README.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/Beutl.Api/Services/LocalPackage.cs
  • src/Beutl.Api/Services/PackageManager.cs
  • tests/Beutl.UnitTests/Api/LocalPackageTests.cs
  • tests/Beutl.Extensibility.Abstractions.Tests/ExtensibilityAbstractionsAssemblyTests.cs

📝 Walkthrough

Walkthrough

Adds a new Beutl.Extensibility.Abstractions project, updates SDK auto-reference and pack wiring for it, adjusts runtime package handling to recognize it, and adds tests covering assembly boundaries, version selection, and settings subscription cleanup.

Changes

Extensibility abstractions split

Layer / File(s) Summary
Project split and wiring
src/Beutl.Extensibility.Abstractions/*, src/Beutl.Extensibility/Beutl.Extensibility.csproj, Beutl.slnx, nukebuild/Build.cs, AGENTS.md
Creates the abstractions project, references it from Beutl.Extensibility, adds both projects to the solution, updates NuGet packing, and updates the module boundary map.
SDK auto-reference
sdk/Beutl.Extensibility.Sdk/Sdk/Sdk.targets, sdk/README.md
Adds the abstractions auto-reference property, emits the new package reference in both SDK branches, and documents the opt-out options and abstractions-only plugin setup.
Runtime dependency mapping
src/Beutl.Api/Services/CoreLibraries.cs, src/Beutl.Api/Services/LocalPackage.cs
Adds Beutl.Extensibility.Abstractions to runtime dependency version mapping and to the LocalPackage target-version fallback order.
Settings handler lifecycle
src/Beutl.Api/Services/PackageManager.cs
Moves settings-changed handler tracking to a ConditionalWeakTable and rewires setup and cleanup around ConfigurationChanged subscriptions.
Validation coverage
tests/Beutl.Extensibility.Abstractions.Tests/*, tests/Beutl.UnitTests/Api/LocalPackageTests.cs, tests/Beutl.UnitTests/Api/PackageManagerExtensionLifecycleTests.cs
Adds tests for the abstractions assembly boundary, LocalPackage version precedence, and PackageManager subscription lifecycle.

Sequence Diagram(s)

sequenceDiagram
  participant PackageManager
  participant Extension
  participant ExtensionSettings
  participant ConditionalWeakTable
  PackageManager->>ConditionalWeakTable: remove prior subscription for Extension
  PackageManager->>ExtensionSettings: subscribe ConfigurationChanged
  ExtensionSettings->>PackageManager: raise ConfigurationChanged
  PackageManager->>ExtensionSettings: save settings
  PackageManager->>ConditionalWeakTable: store SettingsSubscription
  PackageManager->>ConditionalWeakTable: remove subscription on cleanup
  PackageManager->>ExtensionSettings: unsubscribe stored handler
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A bunny found a contract, soft and small,
Then hopped through SDK gates and pack walls.
With ears held high, I cheered the trace,
For abstractions now had their own cozy place. 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: moving the base extension contracts into abstractions.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch yuto-trd/thin-extensibility-boundary

Comment @coderabbitai help to get the list of available commands.

Extract the root extension contracts into Beutl.Extensibility.Abstractions so
plugin authors can depend on the lightweight base surface without pulling the
full UI/media extension assembly. Keep UI, media, property-editor, and built-in
primitive extension contracts in Beutl.Extensibility.

Move extension settings persistence handler state out of the Extension contract
and into PackageManager, tracked in a ConditionalWeakTable so a handler entry can
never outlive its extension (and its collectible AssemblyLoadContext). Teach
package metadata parsing to recognize Beutl.Extensibility.Abstractions
dependencies.

Publish Beutl.Extensibility.Abstractions as its own package and expose it through
Beutl.Extensibility.Sdk via the BeutlAutoReferenceExtensibilityAbstractions
opt-in, so an author can drop the heavy assembly and reference only the thin
contracts. Cover the handler lifecycle, the dependency-id fallback precedence,
and the abstractions dependency-closure thinness invariant with NUnit tests.

Refs: Project #9 "AI Review" item "設計改善: 拡張 API の境界を薄くする"

BREAKING CHANGE: Extension, ExtensionSettings, and ExportAttribute moved from the
Beutl.Extensibility assembly to Beutl.Extensibility.Abstractions while keeping the
Beutl.Extensibility namespace. Downstream plugins that reference these root
contracts must reference the new Beutl.Extensibility.Abstractions package/assembly
or rebuild against packages that transitively include it.
@yuto-trd yuto-trd force-pushed the yuto-trd/thin-extensibility-boundary branch from e01ed9f to ed906be Compare June 25, 2026 05:22
@yuto-trd yuto-trd marked this pull request as ready for review June 25, 2026 05:24
Copilot AI review requested due to automatic review settings June 25, 2026 05:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the plugin API boundary by extracting the core extension entry-point contracts (Extension, ExtensionSettings, ExportAttribute) into a new lightweight Beutl.Extensibility.Abstractions project/assembly while keeping the Beutl.Extensibility namespace, and adjusts package/runtime plumbing to recognize the new assembly.

Changes:

  • Added Beutl.Extensibility.Abstractions project and tests to enforce dependency thinness and contract placement.
  • Moved extension-settings change-subscription bookkeeping into PackageManager and added lifecycle tests to prevent leaked subscriptions.
  • Updated local package target-version detection, runtime dependency enumeration, SDK auto-references, and build/solution registration to include the new abstractions package.

Reviewed changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/Beutl.UnitTests/Api/PackageManagerExtensionLifecycleTests.cs Adds tests for settings subscription setup/repeat setup/unload cleanup.
tests/Beutl.UnitTests/Api/LocalPackageTests.cs Adds tests for target version selection precedence including the new abstractions dependency.
tests/Beutl.Extensibility.Abstractions.Tests/ExtensibilityAbstractionsAssemblyTests.cs Verifies base contracts live in the abstractions assembly and enforces a “thin” reference closure.
tests/Beutl.Extensibility.Abstractions.Tests/Beutl.Extensibility.Abstractions.Tests.csproj Adds a new test project targeting net10.0 and referencing the abstractions project.
src/Beutl.Extensibility/Beutl.Extensibility.csproj Layers the full extensibility project on top of the new abstractions project.
src/Beutl.Extensibility.Abstractions/ExtensionSettings.cs Introduces ExtensionSettings in the abstractions assembly (still in Beutl.Extensibility namespace).
src/Beutl.Extensibility.Abstractions/Extension.cs Moves Extension into abstractions and removes settings-handler bookkeeping from the contract.
src/Beutl.Extensibility.Abstractions/ExportAttribute.cs Introduces ExportAttribute in the abstractions assembly.
src/Beutl.Extensibility.Abstractions/Beutl.Extensibility.Abstractions.csproj Defines the new abstractions project and its minimal project references.
src/Beutl.Api/Services/PackageManager.cs Tracks settings-change handlers in PackageManager (instead of on Extension) to support clean unloads.
src/Beutl.Api/Services/LocalPackage.cs Recognizes Beutl.Extensibility.Abstractions when determining TargetVersion.
src/Beutl.Api/Services/CoreLibraries.cs Includes Beutl.Extensibility.Abstractions in runtime dependency collection.
sdk/README.md Documents the new auto-reference option for abstractions-only plugins and usage example.
sdk/Beutl.Extensibility.Sdk/Sdk/Sdk.targets Adds BeutlAutoReferenceExtensibilityAbstractions and conditionally emits the abstractions package reference.
nukebuild/Build.cs Ensures the new abstractions package is considered in build packaging/version flows.
Beutl.slnx Adds the new abstractions project and its test project to the solution.
AGENTS.md Updates the module boundary map to describe the new abstractions layer and its intended dependency profile.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/Beutl.Api/Services/PackageManager.cs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/Beutl.Api/Services/CoreLibraries.cs (1)

78-78: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a direct test for this runtime-map entry.

IncludedInRuntimeDependencies feeds both plugin loading and package-install resolution, but the changed test set never exercises the new Beutl.Extensibility.Abstractions branch. Please pin this with a small NUnit test in tests/Beutl.UnitTests/ so the boundary cannot regress silently. Based on learnings, New logic must include a NUnit test in the matching tests/ project (for example, tests/Beutl.UnitTests/ for general unit tests or tests/SourceGeneratorTest/ for generator changes).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Beutl.Api/Services/CoreLibraries.cs` at line 78, Add a direct NUnit test
in tests/Beutl.UnitTests that exercises the IncludedInRuntimeDependencies
runtime-map branch for Beutl.Extensibility.Abstractions. Use the CoreLibraries
entry point and the IncludedInRuntimeDependencies method to verify this package
is treated as included, so the new case is covered explicitly and cannot regress
unnoticed.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/Beutl.Api/Services/LocalPackage.cs`:
- Around line 41-43: The package lookup in LocalPackage’s dependency selection
uses exact string comparisons, so it can miss valid NuGet dependencies when
casing differs. Update the PackageDependency search in the sdkDep assignment to
compare package IDs case-insensitively for Beutl.Sdk, Beutl.Extensibility, and
Beutl.Extensibility.Abstractions, and keep the existing fallback order so
TargetVersion can still be resolved.

In `@src/Beutl.Api/Services/PackageManager.cs`:
- Around line 30-33: The subscription bookkeeping in PackageManager is using
only the Extension as the key, but CleanupExtensionSettings must unsubscribe
from the exact ExtensionSettings instance that SetupExtensionSettings subscribed
to. Update the handler tracking around SetupExtensionSettings and
CleanupExtensionSettings to store both the subscribed ExtensionSettings
publisher and its ConfigurationChanged delegate per extension, then remove the
delegate from that stored publisher during cleanup even if extension.Settings
has changed or been cleared. Keep the fix localized to the PackageManager
subscription/unsubscription flow and the related handler map.

In
`@tests/Beutl.Extensibility.Abstractions.Tests/ExtensibilityAbstractionsAssemblyTests.cs`:
- Around line 7-8: Add the NUnit [TestFixture] attribute to the
ExtensibilityAbstractionsAssemblyTests class so it matches the repo’s test
convention; update the class declaration in
ExtensibilityAbstractionsAssemblyTests and keep the existing [Test] methods
unchanged.

In `@tests/Beutl.UnitTests/Api/LocalPackageTests.cs`:
- Around line 7-8: Add the NUnit [TestFixture] attribute to the
LocalPackageTests class so it is recognized as a test fixture and matches the
repo’s NUnit conventions; keep the existing [Test] methods unchanged and place
the attribute directly on the class declaration.

---

Nitpick comments:
In `@src/Beutl.Api/Services/CoreLibraries.cs`:
- Line 78: Add a direct NUnit test in tests/Beutl.UnitTests that exercises the
IncludedInRuntimeDependencies runtime-map branch for
Beutl.Extensibility.Abstractions. Use the CoreLibraries entry point and the
IncludedInRuntimeDependencies method to verify this package is treated as
included, so the new case is covered explicitly and cannot regress unnoticed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: bab12559-91fb-4a32-971a-757969b17c41

📥 Commits

Reviewing files that changed from the base of the PR and between 92466a5 and ed906be.

📒 Files selected for processing (17)
  • AGENTS.md
  • Beutl.slnx
  • nukebuild/Build.cs
  • sdk/Beutl.Extensibility.Sdk/Sdk/Sdk.targets
  • sdk/README.md
  • src/Beutl.Api/Services/CoreLibraries.cs
  • src/Beutl.Api/Services/LocalPackage.cs
  • src/Beutl.Api/Services/PackageManager.cs
  • src/Beutl.Extensibility.Abstractions/Beutl.Extensibility.Abstractions.csproj
  • src/Beutl.Extensibility.Abstractions/ExportAttribute.cs
  • src/Beutl.Extensibility.Abstractions/Extension.cs
  • src/Beutl.Extensibility.Abstractions/ExtensionSettings.cs
  • src/Beutl.Extensibility/Beutl.Extensibility.csproj
  • tests/Beutl.Extensibility.Abstractions.Tests/Beutl.Extensibility.Abstractions.Tests.csproj
  • tests/Beutl.Extensibility.Abstractions.Tests/ExtensibilityAbstractionsAssemblyTests.cs
  • tests/Beutl.UnitTests/Api/LocalPackageTests.cs
  • tests/Beutl.UnitTests/Api/PackageManagerExtensionLifecycleTests.cs

Comment thread src/Beutl.Api/Services/LocalPackage.cs Outdated
Comment thread src/Beutl.Api/Services/PackageManager.cs Outdated
Comment thread tests/Beutl.UnitTests/Api/LocalPackageTests.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ed906beba1

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread sdk/README.md Outdated
Comment thread sdk/Beutl.Extensibility.Sdk/Sdk/Sdk.targets Outdated
- PackageManager: capture the subscribed ExtensionSettings publisher alongside
  the handler and unsubscribe before Restore, so a repeated SetupExtensionSettings
  cannot Save mid-restore and cleanup unsubscribes from the exact publisher even
  if Settings was swapped or cleared (keeps the collectible AssemblyLoadContext
  collectible).
- LocalPackage: compare Beutl dependency ids case-insensitively, since NuGet
  package ids are case-insensitive.
- Beutl.Extensibility.Sdk: in the prevent-copy block reference
  Beutl.Extensibility.Abstractions directly whenever it is in the closure so its
  runtime asset is excluded from sideload output; document the all-off +
  abstractions-on path for a truly thin plugin (ProjectSystem / NodeGraph /
  Editor otherwise pull Beutl.Extensibility back transitively).
- Add [TestFixture] to the new NUnit fixtures.
@github-actions

Copy link
Copy Markdown
Contributor

No TODO comments were found.

@github-actions

Copy link
Copy Markdown
Contributor

Code Coverage

Package Line Rate Branch Rate Complexity Health
Beutl.Api 12% 7% 1178
Beutl.Configuration 41% 22% 350
Beutl.Controls 0% 0% 5513
Beutl.Core 62% 56% 3069
Beutl.Editor 78% 70% 1742
Beutl.Editor.Components 1% 0% 8552
Beutl.Embedding.MediaFoundation 6% 8% 1376
Beutl.Engine 63% 52% 18123
Beutl.Engine.SourceGenerators 59% 44% 540
Beutl.Extensibility 40% 59% 106
Beutl.Extensibility.Abstractions 86% 100% 5
Beutl.Extensions.AVFoundation 5% 12% 246
Beutl.Extensions.FFmpeg 7% 8% 861
Beutl.FFmpegIpc 25% 33% 825
Beutl.Language 7% 50% 1349
Beutl.NodeGraph 24% 15% 2474
Beutl.ProjectSystem 58% 43% 1061
Beutl.Threading 100% 90% 137
Beutl.Utilities 94% 87% 358
Summary 35% (43678 / 124707) 31% (10887 / 35522) 47865

Minimum allowed line rate is 0%

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.

2 participants