Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,39 @@ JitPack continue to resolve through the existing coordinates.
Method-level `@since` backfill for the ~380 public methods in these
packages is intentionally out of scope here and tracked separately.

### Public API

- **New `@Beta` annotation** (Track H2). Companion to the existing
[`@Internal`](src/main/java/com/demcha/compose/document/api/Internal.java)
marker:
[`com.demcha.compose.document.api.Beta`](src/main/java/com/demcha/compose/document/api/Beta.java)
signals an **Extension SPI** or **Experimental** surface — a
deliberately-exposed seam library users can implement or call, but
whose shape may still evolve between minor releases per the
[API stability policy](docs/api-stability.md) § 1. First application:
[`com.demcha.compose.document.layout.NodeDefinition`](src/main/java/com/demcha/compose/document/layout/NodeDefinition.java)
— the canonical custom-node-type seam, carved out of the otherwise
`@Internal` `document.layout` package. New
`BetaAnnotationDocumentationTest` pins the annotation's retention /
target / `@Documented`-ness / source-Javadoc contract in the same
shape `InternalAnnotationDocumentationTest` already pins for
`@Internal`. Additional Extension SPI surfaces (render-handler
interfaces, fragment-payload interfaces) will gain the marker
incrementally as their contract solidifies.

### Documentation

- **`docs/architecture/package-map.md` updated** alongside H2. A new
intro paragraph documents the stability-marker convention (Stable
default; engine packages are package-level `@Internal`; individual
Extension SPI seams carved out of `@Internal` packages carry
`@Beta`), and the `document.layout` row calls out `NodeDefinition`
as the current `@Beta` seam.
- **`docs/api-stability.md` revised** alongside H2 — `@Beta` annotation
reference cells in §1 are no longer hedged as "pending"; the
associated quote block lists both annotations side-by-side with the
guard tests that pin them.

### Engine internals (no behaviour change)

- **`RowSlots` helper extracted** from `LayoutCompiler` and
Expand Down
22 changes: 13 additions & 9 deletions docs/api-stability.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,22 @@ matrix.
|---|---|---|---|
| **Stable** | _(default — no annotation)_ | The canonical authoring surface that user code is meant to call: `GraphCompose.document(...)`, `DocumentSession`, `DocumentDsl`, `RowBuilder` / `SectionBuilder` / `ParagraphBuilder` and friends, `DocumentInsets` / `DocumentColor` / `DocumentTextStyle`, the `BusinessTheme` and `CvTheme` factories, the recommended template presets in `cv.v2.*` and `coverletter.v2.*`. | **Major releases only.** |
| **Supported** | _(no annotation; called out in the page's Javadoc)_ | A canonical surface that ships through 1.x but won't be in 2.0 — its replacement is already the Stable path. The `cv.presets.*` "classic" CV preset surface is the only Supported tier in 1.x today (replaced by `cv.v2.*` per [`which-template-system.md`](templates/which-template-system.md)). Bug fixes + behaviour-preserving refactors only. | **Minor releases for behaviour-preserving refactors; removed wholesale in 2.0.** |
| **Extension SPI** | `@Beta` _(annotation arriving in a near-term 1.6.x release; this row already describes the policy that will apply)_ | Public extension points that authors are expected to **implement**, not only call: render-handler interfaces, `NodeDefinition`, custom `Theme` subtype contracts, fragment payload interfaces designed for extension. | Minor releases, with a one-minor deprecation window where possible. |
| **Experimental** | `@Beta` _(same annotation as Extension SPI; the distinction lives in the docstring)_ | A brand-new public type shipping in its first minor release before its contract has stabilised. The contract is in active flux. | Any minor release, including removal. No deprecation window. |
| **Extension SPI** | [`@Beta`](../src/main/java/com/demcha/compose/document/api/Beta.java) | Public extension points that authors are expected to **implement**, not only call: render-handler interfaces, [`NodeDefinition`](../src/main/java/com/demcha/compose/document/layout/NodeDefinition.java), custom `Theme` subtype contracts, fragment payload interfaces designed for extension. | Minor releases, with a one-minor deprecation window where possible. |
| **Experimental** | [`@Beta`](../src/main/java/com/demcha/compose/document/api/Beta.java) _(same annotation as Extension SPI; the distinction lives in the docstring on the annotated element)_ | A brand-new public type shipping in its first minor release before its contract has stabilised. The contract is in active flux. | Any minor release, including removal. No deprecation window. |
| **Internal** | [`@Internal`](../src/main/java/com/demcha/compose/document/api/Internal.java) (per-element or per-package) | Engine surface: everything in `com.demcha.compose.document.layout.*`, `com.demcha.compose.engine.*`, render-pipeline payload records, `LayoutCompiler`, `NodeDefinitionSupport`, the placement / measure / split contracts. Technically `public` for cross-package collaboration; not part of the contract. Canonical list lives in [ADR-0003](adr/0003-api-stability-and-internal-marker.md) § *Coverage*. | **Any release.** No deprecation window, no CHANGELOG entry required. |
| **Legacy** | _(no annotation today; flagged in [`which-template-system.md`](templates/which-template-system.md) § 4 and in CHANGELOG `### Deprecations`)_ | Pre-rebuild surface kept only so downstream callers from before the v1.6 rebuild keep compiling: `com.demcha.templates.*` (the original `MainPageCV` / `MainPageCvDTO` / `ModuleYml` / `TemplateBuilder` family), `com.demcha.compose.v2.*` (the original engine-direct builders). Frozen — bug fixes only. | **Removed in 2.0**; no patch / minor changes other than security fixes. |

> The repo's [`@Internal`](../src/main/java/com/demcha/compose/document/api/Internal.java)
> annotation already exists and is enforced by
> [`InternalAnnotationCoverageTest`](../src/test/java/com/demcha/documentation/InternalAnnotationCoverageTest.java)
> and `InternalAnnotationDocumentationTest`. The `@Beta` annotation is
> still pending — until it lands, Extension SPI and Experimental tiers
> are signalled in Javadoc prose only; this page describes the policy
> the annotation will encode once it ships.
> Both marker annotations
> ([`@Internal`](../src/main/java/com/demcha/compose/document/api/Internal.java)
> and [`@Beta`](../src/main/java/com/demcha/compose/document/api/Beta.java))
> live in the public `document.api` package and are pinned by
> [`InternalAnnotationCoverageTest`](../src/test/java/com/demcha/documentation/InternalAnnotationCoverageTest.java),
> `InternalAnnotationDocumentationTest`, and `BetaAnnotationDocumentationTest`.
> The Extension SPI seam currently carrying `@Beta` is
> [`NodeDefinition`](../src/main/java/com/demcha/compose/document/layout/NodeDefinition.java);
> additional Extension SPI surfaces (render-handler interfaces,
> fragment-payload interfaces designed for extension) will gain the
> marker incrementally as their contract solidifies.

### What each tier promises

Expand Down
13 changes: 12 additions & 1 deletion docs/architecture/package-map.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

This document is the source of truth for production package ownership in the canonical engine branch.

**Stability markers.** Public packages default to the **Stable** tier of the
[API stability policy](../api-stability.md). Engine packages (`com.demcha.compose.engine.*`,
`com.demcha.compose.document.layout`, render-pipeline internals) carry the
[`@Internal`](../../src/main/java/com/demcha/compose/document/api/Internal.java)
marker at the package level; individual types deliberately exposed as
Extension SPI inside an `@Internal` package carry
[`@Beta`](../../src/main/java/com/demcha/compose/document/api/Beta.java)
on the type itself (`NodeDefinition` is the current example). The
"Extension rule" column below names the extension seam where one is
intended.

## Public Authoring Surface

| Package | Responsibility | Extension rule |
Expand All @@ -20,7 +31,7 @@ This document is the source of truth for production package ownership in the can

| Package | Responsibility | Extension rule |
| --- | --- | --- |
| `com.demcha.compose.document.layout` | Semantic layout compiler, node definitions, fragments, split/measure contracts, and layout graph. | New node behavior must be deterministic and covered by layout or render tests. |
| `com.demcha.compose.document.layout` (`@Internal` at package level) | Semantic layout compiler, node definitions, fragments, split/measure contracts, and layout graph. | `NodeDefinition` is `@Beta` — Extension SPI for custom node types. New node behavior must be deterministic and covered by layout or render tests. |
| `com.demcha.compose.document.backend.fixed` | Backend-neutral fixed-layout rendering contract. | Keep it independent from PDFBox and semantic template data. |
| `com.demcha.compose.document.backend.fixed.pdf` | Canonical fixed-layout PDF backend, fragment handlers, PDF-specific options, and PDF-backed measurement resources. | Keep PDFBox lifecycle internal; normal callers should use `DocumentSession` and default PDF convenience methods. |
| `com.demcha.compose.document.dsl.internal` | Internal helpers for public DSL builders such as semantic name normalization and builder callback application. | Do not expose these helpers in examples; move reusable authoring concepts to public builder classes instead. |
Expand Down
58 changes: 58 additions & 0 deletions src/main/java/com/demcha/compose/document/api/Beta.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package com.demcha.compose.document.api;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Marks a public API element as an <strong>Extension SPI</strong> or
* <strong>Experimental</strong> surface — a deliberately-exposed seam that
* library users can implement or call, but whose shape may still change
* between minor releases.
*
* <p>Elements annotated {@code @Beta} carry the policy spelled out in
* <a href="https://github.com/DemchaAV/GraphCompose/blob/develop/docs/api-stability.md">
* the API stability policy</a> § 1 — broadly:</p>
*
* <ul>
* <li><strong>Extension SPI</strong> usage (the common case): the seam
* is intentionally public so users can implement or call it, but
* the shape may evolve in a minor release. When it does, the
* previous shape ships {@code @Deprecated} for at least one minor
* release first and the CHANGELOG entry calls out the migration
* under {@code ### Public API}.</li>
* <li><strong>Experimental</strong> usage: a brand-new public type
* shipping in its first minor release before the contract has
* stabilised. The contract is in active flux and may change or
* disappear in any minor release without a deprecation window;
* the docstring on the element says so explicitly.</li>
* </ul>
*
* <p>The distinction between the two usages lives in the element's own
* Javadoc, not in this annotation. The annotation itself just signals
* "policy applies — read the page before depending on this".</p>
*
* <p>If your use case requires a stable contract over an
* {@code @Beta}-marked element, please open an issue at
* <a href="https://github.com/DemchaAV/GraphCompose/issues">the
* GraphCompose tracker</a> so the stabilisation can be prioritised.</p>
*
* <p>Architecture guard tests may consume this annotation reflectively;
* the runtime retention is intentional.</p>
*
* @see Internal
* @since 1.6.6
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.TYPE,
ElementType.METHOD,
ElementType.FIELD,
ElementType.CONSTRUCTOR,
ElementType.PACKAGE,
})
public @interface Beta {
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,28 @@
package com.demcha.compose.document.layout;

import com.demcha.compose.document.api.Beta;
import com.demcha.compose.document.node.DocumentNode;

import java.util.List;

/**
* Prepare, split, and fragment emission contract for a semantic node type.
*
* <p><b>Extension SPI seam.</b> This interface is the deliberately-public
* extension point inside the otherwise {@link com.demcha.compose.document.api.Internal
* &#64;Internal} {@code document.layout} package: library users implementing
* custom node types reach for it. The implementation contract — the
* shape of {@link PrepareContext}, {@link FragmentContext},
* {@link BoxConstraints}, {@link MeasureResult}, and friends — may
* evolve in a minor release, with a one-minor deprecation window where
* possible. See the
* <a href="https://github.com/DemchaAV/GraphCompose/blob/develop/docs/api-stability.md">API stability policy</a>
* § 1 (<em>Extension SPI</em> row) for the full contract.</p>
*
* @param <E> semantic node type
* @since 1.0.0
*/
@Beta
public interface NodeDefinition<E extends DocumentNode> {

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.demcha.compose.document.api;

import static org.assertj.core.api.Assertions.assertThat;

import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.nio.file.Files;
import java.nio.file.Path;

import org.junit.jupiter.api.Test;

/**
* Pins the contract of the {@link Beta} annotation in the same shape
* {@link InternalAnnotationDocumentationTest} pins {@link Internal}.
* If the retention, target set, or Javadoc policy drifts, the build
* fails here rather than during a future architecture refactor.
*/
class BetaAnnotationDocumentationTest {

@Test
void annotationHasRuntimeRetentionForArchitectureGuards() {
Retention retention = Beta.class.getAnnotation(Retention.class);
assertThat(retention)
.describedAs("@Beta must declare @Retention so guard tests can read it reflectively")
.isNotNull();
assertThat(retention.value()).isEqualTo(RetentionPolicy.RUNTIME);
}

@Test
void annotationIsDocumentedSoJavadocSurfacesIt() {
assertThat(Beta.class.getAnnotation(Documented.class))
.describedAs("@Beta must be @Documented so Javadoc surfaces the marker on annotated types")
.isNotNull();
}

@Test
void annotationTargetsAllExpectedElementKinds() {
Target target = Beta.class.getAnnotation(Target.class);
assertThat(target).isNotNull();
assertThat(target.value()).contains(
ElementType.TYPE,
ElementType.METHOD,
ElementType.FIELD,
ElementType.CONSTRUCTOR,
ElementType.PACKAGE);
}

@Test
void annotationSourceJavadocStatesTheStabilityContract() throws IOException {
Path source = Path.of("src/main/java/com/demcha/compose/document/api/Beta.java");
String body = Files.readString(source);
assertThat(body)
.describedAs("Javadoc must name both Extension SPI and Experimental usages")
.contains("Extension SPI")
.contains("Experimental");
assertThat(body)
.describedAs("Javadoc must link to the API stability policy page")
.contains("docs/api-stability.md");
assertThat(body)
.describedAs("Javadoc must direct users to the issue tracker for stabilisation requests")
.contains("github.com/DemchaAV/GraphCompose/issues");
}

@Test
void betaAndInternalLiveInTheSameApiPackage() {
assertThat(Beta.class.getPackage().getName())
.describedAs("@Beta should live next to @Internal so IDE autocomplete surfaces both")
.isEqualTo(Internal.class.getPackage().getName());
}
}