diff --git a/README.md b/README.md index 0ec1fdc..983f498 100644 --- a/README.md +++ b/README.md @@ -71,3 +71,18 @@ See [AGENTS.md](AGENTS.md) for architecture and conventions. Licensed under the [Apache License, Version 2.0](LICENSE). Copyright 2025-2026 Risa Labs Inc. + + +### Durable bookmark library contracts (unreleased) + +`BookmarkLibraryProvider` exposes revision-checked saved-record mutations and independent +favorite membership. `BookmarkOpeningProvider` is the shared, window-scoped host opening +route used by the shelf, library and search. Definitions match the corresponding host +`plugin-bookmark-types` module in [BossConsole PR #759](https://github.com/risa-labs-inc/BossConsole/pull/759). + +The SDK supplies types, not host implementations. A plugin requiring the library must +check `context.getPluginAPI(BookmarkOpeningProvider::class.java)` before initializing +storage or registering its library providers, and refuse activation when unavailable. +When this SDK change is released, consumers should pin that actual release and declare +its `minApiVersion`; no future SDK or BOSS release number is assigned by this change. +The existing release workflow publishes the next SDK release after a maintainer merges it. diff --git a/api/boss-plugin-api.api b/api/boss-plugin-api.api index 77475f8..4c047a5 100644 --- a/api/boss-plugin-api.api +++ b/api/boss-plugin-api.api @@ -7111,6 +7111,118 @@ public final class ai/rever/boss/plugin/bookmark/BookmarkCollection$Companion { public final fun serializer ()Lkotlinx/serialization/KSerializer; } +public abstract interface class ai/rever/boss/plugin/bookmark/BookmarkLibraryProvider { + public abstract fun createCollection (Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun deleteBookmark (Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun deleteCollection (Ljava/lang/String;Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun getState ()Lkotlinx/coroutines/flow/StateFlow; + public abstract fun moveBookmark (Ljava/lang/String;Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun reload (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun renameCollection (Ljava/lang/String;Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun saveBookmark (Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun setFavorite (Ljava/lang/String;ZJLkotlin/coroutines/Continuation;)Ljava/lang/Object; + public abstract fun undo (Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class ai/rever/boss/plugin/bookmark/BookmarkLibraryState { + public static final field $stable I + public fun ()V + public fun (Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;)V + public synthetic fun (Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/util/List; + public final fun component2 ()Ljava/util/Set; + public final fun component3 ()Z + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()J + public final fun component6 ()Z + public final fun component7 ()Ljava/util/Set; + public final fun copy (Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;)Lai/rever/boss/plugin/bookmark/BookmarkLibraryState; + public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkLibraryState;Ljava/util/List;Ljava/util/Set;ZLjava/lang/String;JZLjava/util/Set;ILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkLibraryState; + public fun equals (Ljava/lang/Object;)Z + public final fun getCollections ()Ljava/util/List; + public final fun getDefaultFavorite ()Z + public final fun getError ()Ljava/lang/String; + public final fun getFavoriteBookmarkIds ()Ljava/util/Set; + public final fun getReady ()Z + public final fun getRevision ()J + public final fun getUnfiledCollectionIds ()Ljava/util/Set; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class ai/rever/boss/plugin/bookmark/BookmarkMutationResult { + public static final field $stable I + public fun (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + public synthetic fun (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Ljava/lang/String; + public final fun component5 ()Ljava/lang/String; + public final fun component6 ()Ljava/lang/String; + public final fun copy (ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Lai/rever/boss/plugin/bookmark/BookmarkMutationResult; + public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkMutationResult;ZLjava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkMutationResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getBookmarkId ()Ljava/lang/String; + public final fun getCollectionId ()Ljava/lang/String; + public final fun getDuplicateBookmarkId ()Ljava/lang/String; + public final fun getMessage ()Ljava/lang/String; + public final fun getSuccess ()Z + public final fun getUndoToken ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class ai/rever/boss/plugin/bookmark/BookmarkOpenResult { + public static final field $stable I + public fun (ZLjava/lang/String;Ljava/lang/String;Z)V + public synthetic fun (ZLjava/lang/String;Ljava/lang/String;ZILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Z + public final fun component2 ()Ljava/lang/String; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Z + public final fun copy (ZLjava/lang/String;Ljava/lang/String;Z)Lai/rever/boss/plugin/bookmark/BookmarkOpenResult; + public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkOpenResult;ZLjava/lang/String;Ljava/lang/String;ZILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkOpenResult; + public fun equals (Ljava/lang/Object;)Z + public final fun getMessage ()Ljava/lang/String; + public final fun getReused ()Z + public final fun getSuccess ()Z + public final fun getTabId ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public abstract interface class ai/rever/boss/plugin/bookmark/BookmarkOpeningProvider { + public abstract fun openBookmark (Lai/rever/boss/plugin/bookmark/Bookmark;Ljava/lang/String;Ljava/lang/String;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + +public final class ai/rever/boss/plugin/bookmark/BookmarkSaveRequest { + public static final field $stable I + public fun (Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;)V + public synthetic fun (Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/String; + public final fun component2 ()Lai/rever/boss/plugin/workspace/TabConfig; + public final fun component3 ()Ljava/lang/String; + public final fun component4 ()Z + public final fun component5 ()J + public final fun component6 ()Ljava/lang/String; + public final fun component7 ()Z + public final fun component8 ()Ljava/lang/String; + public final fun copy (Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;)Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest; + public static synthetic fun copy$default (Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest;Ljava/lang/String;Lai/rever/boss/plugin/workspace/TabConfig;Ljava/lang/String;ZJLjava/lang/String;ZLjava/lang/String;ILjava/lang/Object;)Lai/rever/boss/plugin/bookmark/BookmarkSaveRequest; + public fun equals (Ljava/lang/Object;)Z + public final fun getAllowCopy ()Z + public final fun getBookmarkId ()Ljava/lang/String; + public final fun getCollectionId ()Ljava/lang/String; + public final fun getExpectedRevision ()J + public final fun getFavorite ()Z + public final fun getName ()Ljava/lang/String; + public final fun getTabConfig ()Lai/rever/boss/plugin/workspace/TabConfig; + public final fun getWorkspaceName ()Ljava/lang/String; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + public final class ai/rever/boss/plugin/bookmark/FavoriteWorkspace { public static final field $stable I public static final field Companion Lai/rever/boss/plugin/bookmark/FavoriteWorkspace$Companion; diff --git a/build.gradle.kts b/build.gradle.kts index 123b5d7..1e1cf67 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -594,6 +594,9 @@ group = "ai.rever.boss.plugin.bundled" // CLI selection, rather than asking a consumer to duplicate routing policy. New types only: // ApiClassLoader/minApiVersion, no host release. // +// Unreleased: adds durable bookmark library and shared opening contracts. +// SDK availability does not imply a host supplies BookmarkOpeningProvider; +// consumers must check that capability before activating/migrating a library. version = "1.0.93" java { diff --git a/src/main/kotlin/ai/rever/boss/plugin/bookmark/BookmarkLibraryProvider.kt b/src/main/kotlin/ai/rever/boss/plugin/bookmark/BookmarkLibraryProvider.kt new file mode 100644 index 0000000..5709892 --- /dev/null +++ b/src/main/kotlin/ai/rever/boss/plugin/bookmark/BookmarkLibraryProvider.kt @@ -0,0 +1,107 @@ +package ai.rever.boss.plugin.bookmark + +import ai.rever.boss.plugin.workspace.TabConfig +import kotlinx.coroutines.flow.StateFlow + +/** + * Optional durable library capability. Older providers remain readable through their existing API. + * + * The SDK makes these types available; it does not install the matching host behavior. + * Consumers must verify [BookmarkOpeningProvider] is registered before activating a + * library that requires the shared opening route. Keep these definitions binary-compatible + * with the host's plugin-bookmark-types module, which is loaded parent-first. + */ +interface BookmarkLibraryProvider { + val state: StateFlow + + suspend fun saveBookmark(request: BookmarkSaveRequest): BookmarkMutationResult + + suspend fun setFavorite( + bookmarkId: String, + favorite: Boolean, + expectedRevision: Long, + ): BookmarkMutationResult + + suspend fun moveBookmark( + bookmarkId: String, + collectionId: String, + expectedRevision: Long, + ): BookmarkMutationResult + + suspend fun deleteBookmark( + bookmarkId: String, + expectedRevision: Long, + ): BookmarkMutationResult + + suspend fun undo( + token: String, + expectedRevision: Long, + ): BookmarkMutationResult + + suspend fun createCollection( + name: String, + expectedRevision: Long, + ): BookmarkMutationResult + + suspend fun renameCollection( + collectionId: String, + name: String, + expectedRevision: Long, + ): BookmarkMutationResult + + suspend fun deleteCollection( + collectionId: String, + moveToCollectionId: String?, + expectedRevision: Long, + ): BookmarkMutationResult + + suspend fun reload() +} + +data class BookmarkLibraryState( + val collections: List = emptyList(), + val favoriteBookmarkIds: Set = emptySet(), + val ready: Boolean = false, + val error: String? = null, + val revision: Long = 0, + val defaultFavorite: Boolean = true, + /** Storage containers shown as root bookmarks rather than user-created folders. */ + val unfiledCollectionIds: Set = emptySet(), +) + +data class BookmarkSaveRequest( + val collectionId: String, + val tabConfig: TabConfig, + val name: String, + val favorite: Boolean, + val expectedRevision: Long, + val bookmarkId: String? = null, + val allowCopy: Boolean = false, + val workspaceName: String = "", +) + +data class BookmarkMutationResult( + val success: Boolean, + val bookmarkId: String? = null, + val collectionId: String? = null, + val duplicateBookmarkId: String? = null, + val undoToken: String? = null, + val message: String? = null, +) + +/** One host opening route for the sidebar, library and search. No saved command is executed on reuse. */ +interface BookmarkOpeningProvider { + suspend fun openBookmark( + bookmark: Bookmark, + windowId: String, + panelId: String?, + forceNewTab: Boolean, + ): BookmarkOpenResult +} + +data class BookmarkOpenResult( + val success: Boolean, + val message: String? = null, + val tabId: String? = null, + val reused: Boolean = false, +) diff --git a/src/test/kotlin/ai/rever/boss/plugin/bookmark/BookmarkLibraryLinkageTest.kt b/src/test/kotlin/ai/rever/boss/plugin/bookmark/BookmarkLibraryLinkageTest.kt new file mode 100644 index 0000000..d11e7c2 --- /dev/null +++ b/src/test/kotlin/ai/rever/boss/plugin/bookmark/BookmarkLibraryLinkageTest.kt @@ -0,0 +1,17 @@ +package ai.rever.boss.plugin.bookmark + +import kotlin.test.Test +import kotlin.test.assertEquals +import java.lang.reflect.Modifier + +/** Host and SDK copies must both provide fields emitted into consuming Compose plugins. */ +class BookmarkLibraryLinkageTest { + @Test fun `new bookmark models expose public static Compose stability fields`() { + listOf(BookmarkLibraryState::class.java, BookmarkSaveRequest::class.java, + BookmarkMutationResult::class.java, BookmarkOpenResult::class.java).forEach { type -> + val field = type.getField("\$stable") + assertEquals(Int::class.javaPrimitiveType, field.type, type.name) + assertEquals(true, Modifier.isStatic(field.modifiers), type.name) + } + } +}