diff --git a/.github/workflows/publishRelease.yml b/.github/workflows/publishRelease.yml index 68b2dbe..4e36419 100644 --- a/.github/workflows/publishRelease.yml +++ b/.github/workflows/publishRelease.yml @@ -16,7 +16,10 @@ jobs: needs: test name: Publish Production environment: production - runs-on: ubuntu-latest + # MUST be macOS: Kotlin/Native Apple targets (iosArm64/iosX64/iosSimulatorArm64) can only be + # built on a macOS host. On a Linux runner those tasks are silently skipped, so the iOS klibs + # never get uploaded (the build still goes green) — exactly what happened for 3.1.5–4.1.0. + runs-on: macos-latest steps: - name: Check out code uses: actions/checkout@v4 diff --git a/CHANGELOG.md b/CHANGELOG.md index 77874e6..09bc313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to this project are documented here. The format is based on ## [Unreleased] +## [4.1.1] - 2026-06-08 + +### Fixed +- Restored the Apple/iOS artifacts (`iosArm64`, `iosX64`, `iosSimulatorArm64`) for the core and every + module. The release workflow published from a Linux runner, which cannot build Kotlin/Native Apple + targets, so they were silently omitted from 3.1.5–4.1.0; publishing now runs on macOS. No API or + behavior changes from 4.1.0 — this is a packaging fix only. + ## [4.1.0] - 2026-06-07 ### Added @@ -91,7 +99,8 @@ A breaking release that cleans up the core API and enforces an explicit public s ## [3.1.3] - iOS targets and Maven Central (Sonatype Central Portal) publishing. -[Unreleased]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v4.1.0...HEAD +[Unreleased]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v4.1.1...HEAD +[4.1.1]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v4.1.0...v4.1.1 [4.1.0]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v4.0.0...v4.1.0 [4.0.0]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v3.4.0...v4.0.0 [3.4.0]: https://github.com/AdrianKuta/Tree-Data-Structure/compare/v3.1.5...v3.4.0 diff --git a/README.md b/README.md index 73c2fb5..1ec1c2a 100644 --- a/README.md +++ b/README.md @@ -30,14 +30,14 @@ Gradle (Kotlin DSL): ```kotlin // commonMain for KMP projects, or any sourceSet/module where you need it dependencies { - implementation("com.github.adriankuta:tree-structure:4.1.0") // latest version is on the badge above + implementation("com.github.adriankuta:tree-structure:4.1.1") // latest version is on the badge above } ``` Gradle (Groovy): ```groovy dependencies { - implementation "com.github.adriankuta:tree-structure:4.1.0" + implementation "com.github.adriankuta:tree-structure:4.1.1" } ``` @@ -46,7 +46,7 @@ Maven: com.github.adriankuta tree-structure - 4.1.0 + 4.1.1 ``` @@ -160,7 +160,7 @@ that depends on the core. `@Serializable` directly. Convert to and from the acyclic `TreeNodeDto` instead. ```kotlin -implementation("com.github.adriankuta:tree-structure-serialization:4.1.0") +implementation("com.github.adriankuta:tree-structure-serialization:4.1.1") ``` ```kotlin val json = Json.encodeToString(root.toDto()) @@ -172,7 +172,7 @@ val restored = Json.decodeFromString>(json).toTreeNode() Traverse a tree as a cold `Flow`, which is handy inside coroutine and `ViewModel` pipelines. ```kotlin -implementation("com.github.adriankuta:tree-structure-coroutines:4.1.0") +implementation("com.github.adriankuta:tree-structure-coroutines:4.1.1") ``` ```kotlin root.preOrderFlow().collect { println(it.value) } @@ -185,7 +185,7 @@ A `LazyTree` composable for Compose Multiplatform (JVM/desktop, iOS, Wasm). Only are composed, and you decide how each node looks: ```kotlin -implementation("com.github.adriankuta:tree-structure-compose:4.1.0") +implementation("com.github.adriankuta:tree-structure-compose:4.1.1") ``` ```kotlin LazyTree(root) { node, depth, expanded, toggle -> @@ -204,7 +204,7 @@ subtrees are reused, so updates are cheap and old roots stay valid. Backed by `kotlinx.collections.immutable`. ```kotlin -implementation("com.github.adriankuta:tree-structure-immutable:4.1.0") +implementation("com.github.adriankuta:tree-structure-immutable:4.1.1") ``` ```kotlin val root = ImmutableTreeNode("World").addChild(ImmutableTreeNode("Europe")) diff --git a/build.gradle.kts b/build.gradle.kts index 18e36ee..244b730 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -11,7 +11,7 @@ plugins { val PUBLISH_GROUP_ID = "com.github.adriankuta" val PUBLISH_ARTIFACT_ID = "tree-structure" // base artifact; KMP will add -jvm, -ios*, etc. -val PUBLISH_VERSION = "4.1.0" +val PUBLISH_VERSION = "4.1.1" val snapshot: String? by project diff --git a/docs/superpowers/specs/2026-06-07-tree-enhancements-design.md b/docs/superpowers/specs/2026-06-07-tree-enhancements-design.md new file mode 100644 index 0000000..d327d4a --- /dev/null +++ b/docs/superpowers/specs/2026-06-07-tree-enhancements-design.md @@ -0,0 +1,77 @@ +# Design — four additive enhancements to `tree-structure` (target 4.1.0) + +Date: 2026-06-07. Source issues: #34, #35, #36, #33. Integration: **four separate PRs**, +one per issue (`Closes #NN`), matching the repo's per-issue PR convention (#32). + +## Shared rules (every issue) + +- `explicitApi()` is on everywhere → every new public declaration is `public` with KDoc. +- New tests live in `commonTest`, use `kotlin("test")`, and mirror the existing test style + (`TreeNodeV4Test.kt`, `TreeNodeNavigationTest.kt`, …). +- Add a bullet to `CHANGELOG.md` under `## [Unreleased]` → `### Added` (do **not** bump + `PUBLISH_VERSION`; releases are a separate manual step). +- Regenerate the binary-compatibility baseline with `./gradlew apiDump`; verify with `apiCheck`. +- Do not modify the core public surface in a breaking way — these are additive only. + +## #34 — Structural mutation helpers → **members on `TreeNode`** + +Members (not extensions) because they need the private `_children`/`_parent` and the cycle/parent +validation. + +- Extract the existing inline validation from `addChild` into a private + `validateAttachable(child)` and reuse it. +- `insertChild(index: Int, child)` — validated insert at index (bounds `0.._children.size`). +- `removeChildAt(index: Int): TreeNode` — remove and return the detached child. +- `replaceChild(index: Int, child): TreeNode` — swap, return the old (now detached) child. +- `moveChild(child, toIndex): Boolean` — reorder an existing direct child; no re-parent/cycle + check needed (it is already a child); `false` if `child` is not a direct child. +- `addChildren(vararg children)` — validated append of several (per-child `addChild` semantics). +- `sortChildren(comparator: Comparator>)` — stable in-place reorder. + +## #35 — Query algorithms → **extensions** in new `TreeNodeQueryExt.kt` + +Built on the public API (`ancestors()`, `parent`, `depth()`, sequences). Return `null` when the +two nodes are unrelated (different trees). + +- `lowestCommonAncestor(other): TreeNode?` — deepest common node; includes self/other as + candidates (`LCA(a, a) == a`, `LCA(a, descendantOfA) == a`). +- `distance(other): Int?` — `depth(this) + depth(other) - 2 * depth(lca)`. +- `pathBetween(other): List>?` — `[this … lca … other]`. +- `contains(value): Boolean` — value search over the subtree, including the receiver. + +Document complexity (parent-walk based; O(depth) for LCA/distance, O(n) for `contains`). + +## #36 — Customizable `prettyString()` → **extension** in new `TreeNodePrettyPrintExt.kt` + +- `data class TreeConnectors(branch, lastBranch, vertical, empty)` with a `companion`: + `Default` (current Unicode box-drawing) and `Ascii`. +- `fun TreeNode.prettyString(connectors = TreeConnectors.Default, + render: (value: T, depth: Int, isLast: Boolean) -> String = { v, _, _ -> v.toString() }): String`. +- The no-arg member `prettyString()` is unchanged; member resolution wins for `node.prettyString()`, + so existing behaviour and output are byte-for-byte identical. + +## #33 — Immutable variant → **new module `:tree-structure-immutable`** + +Largest item; fully isolated from the other three except for `settings.gradle.kts`, +`libs.versions.toml`, the root `build.gradle.kts` Dokka aggregation block, and `CHANGELOG.md`. + +- `settings.gradle.kts`: `include(":tree-structure-immutable")`. +- New `build.gradle.kts` mirroring the serialization module's KMP target matrix; deps + `api(project(":"))` + `kotlinx-collections-immutable`. +- `libs.versions.toml`: add `kotlinx-collections-immutable` (a recent stable, e.g. 0.3.8) and + wire it. +- Root `build.gradle.kts`: add `dokka(project(":tree-structure-immutable"))` to the aggregation. +- `ImmutableTreeNode` backed by `persistentListOf` children; `addChild` / `removeChild` / + `mapValues` return a new root with unchanged subtrees structurally shared. Mirror pre/post/ + level-order traversal helpers. +- Its own `tree-structure-immutable/api/` baseline via `apiDump`. + +## Orchestration + +- Four git worktrees off `origin/master`, one branch each (`feat/...`), for full isolation — no + collisions on `TreeNode.kt`, `api/*.api`, or `CHANGELOG.md`. +- A workflow pipelines each issue: implement (code + KDoc + tests + CHANGELOG + `apiDump`) and + verify locally (`jvmTest` + `apiCheck`), then an adversarial reviewer checks issue-conformance, + test quality, invariants, explicit API, `.api` freshness, and that it builds. Local checks are + JVM-only for speed; CI validates the full KMP matrix on each PR. +- Then push each passing branch and open a PR (`Closes #NN`); remove the worktrees afterward.