From db85c9822957365fb7a17feafb90eee221d074e2 Mon Sep 17 00:00:00 2001 From: Russ White <356303+russwyte@users.noreply.github.com> Date: Mon, 27 Jul 2026 17:41:12 -0500 Subject: [PATCH 1/2] Bump Specular to 0.10.0 for chalkboard hub chrome. Pulls early-effect-docs-theme chalkboard tokens, widens the catalog, pins the landing footer, and bumps zipx to 0.0.12. --- .github/workflows/ci.yml | 108 ++++++++++++++++-- build.sbt | 4 +- project/plugins.sbt | 7 +- src/main/scala/earlyeffect/hub/BuildHub.scala | 26 ++++- 4 files changed, 124 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c0e8e48..54e3c39 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,9 @@ name: CI tags: - v[0-9]+.[0-9]+.[0-9]+ pull_request: null +concurrency: + group: CI-${{ github.ref }} + cancel-in-progress: ${{ !startsWith(github.ref, 'refs/tags/') }} jobs: verify-gate: name: verify-gate @@ -43,6 +46,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: "0" + fetch-tags: "true" - name: Setup JDK 25 uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: @@ -51,6 +55,32 @@ jobs: - uses: sbt/setup-sbt@6444f4c8111de4b9059c3975def104b03cfaa5f0 # v1.5.2 with: disk-cache: "false" + - name: Resolve cache epoch + id: cache-epoch + run: | + set -euo pipefail + tag_match='v*' + if git remote get-url origin >/dev/null 2>&1; then + remote_count=$(git ls-remote --tags --refs origin "$tag_match" 2>/dev/null | wc -l | tr -d ' ') + local_count=$(git tag -l "$tag_match" | wc -l | tr -d ' ') + if [ "$remote_count" -gt 0 ] && [ "$local_count" -lt "$remote_count" ]; then + echo "::warning title=zipx cache epoch::Local tags ($local_count) fewer than origin ($remote_count). Epoch may be stale; checkout must use fetch-depth: 0 and fetch-tags: true." + fi + fi + if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then + release="${GITHUB_REF#refs/tags/v}" + epoch="$release" + elif tag=$(git describe --tags --abbrev=0 --match "$tag_match" 2>/dev/null); then + release="${tag#v}" + epoch="${release}-ci" + else + echo "::warning title=zipx cache epoch::No tags matching '$tag_match' found; using epoch 0.0.0." + release="0.0.0" + epoch="0.0.0" + fi + echo "epoch=$epoch" >> "$GITHUB_OUTPUT" + echo "release=$release" >> "$GITHUB_OUTPUT" + - name: Cache sbt uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -59,11 +89,11 @@ jobs: ~/.cache/sbt ~/.cache/coursier target - key: ubuntu-latest-jdk25-sbt-0.0.0-ci-${{ github.run_id }}-test + key: ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}-${{ github.run_id }}-test restore-keys: | - ubuntu-latest-jdk25-sbt-0.0.0-ci-${{ github.run_id }}- - ubuntu-latest-jdk25-sbt-0.0.0-ci- - ubuntu-latest-jdk25-sbt-0.0.0- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}-${{ github.run_id }}- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.release }}- ubuntu-latest-jdk25-sbt- - name: test run: sbt 'specularSite' @@ -77,6 +107,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: "0" + fetch-tags: "true" - name: Setup JDK 25 uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: @@ -85,6 +116,32 @@ jobs: - uses: sbt/setup-sbt@6444f4c8111de4b9059c3975def104b03cfaa5f0 # v1.5.2 with: disk-cache: "false" + - name: Resolve cache epoch + id: cache-epoch + run: | + set -euo pipefail + tag_match='v*' + if git remote get-url origin >/dev/null 2>&1; then + remote_count=$(git ls-remote --tags --refs origin "$tag_match" 2>/dev/null | wc -l | tr -d ' ') + local_count=$(git tag -l "$tag_match" | wc -l | tr -d ' ') + if [ "$remote_count" -gt 0 ] && [ "$local_count" -lt "$remote_count" ]; then + echo "::warning title=zipx cache epoch::Local tags ($local_count) fewer than origin ($remote_count). Epoch may be stale; checkout must use fetch-depth: 0 and fetch-tags: true." + fi + fi + if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then + release="${GITHUB_REF#refs/tags/v}" + epoch="$release" + elif tag=$(git describe --tags --abbrev=0 --match "$tag_match" 2>/dev/null); then + release="${tag#v}" + epoch="${release}-ci" + else + echo "::warning title=zipx cache epoch::No tags matching '$tag_match' found; using epoch 0.0.0." + release="0.0.0" + epoch="0.0.0" + fi + echo "epoch=$epoch" >> "$GITHUB_OUTPUT" + echo "release=$release" >> "$GITHUB_OUTPUT" + - name: Cache sbt uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -93,11 +150,11 @@ jobs: ~/.cache/sbt ~/.cache/coursier target - key: ubuntu-latest-jdk25-sbt-0.0.0-ci-${{ github.run_id }}-fmt + key: ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}-${{ github.run_id }}-fmt restore-keys: | - ubuntu-latest-jdk25-sbt-0.0.0-ci-${{ github.run_id }}- - ubuntu-latest-jdk25-sbt-0.0.0-ci- - ubuntu-latest-jdk25-sbt-0.0.0- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}-${{ github.run_id }}- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.release }}- ubuntu-latest-jdk25-sbt- - name: fmt run: sbt 'scalafmtCheckAll' @@ -109,6 +166,7 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: fetch-depth: "0" + fetch-tags: "true" - name: Setup JDK 25 uses: actions/setup-java@03ad4de0992f5dab5e18fcb136590ce7c4a0ac95 # v5.6.0 with: @@ -117,6 +175,32 @@ jobs: - uses: sbt/setup-sbt@6444f4c8111de4b9059c3975def104b03cfaa5f0 # v1.5.2 with: disk-cache: "false" + - name: Resolve cache epoch + id: cache-epoch + run: | + set -euo pipefail + tag_match='v*' + if git remote get-url origin >/dev/null 2>&1; then + remote_count=$(git ls-remote --tags --refs origin "$tag_match" 2>/dev/null | wc -l | tr -d ' ') + local_count=$(git tag -l "$tag_match" | wc -l | tr -d ' ') + if [ "$remote_count" -gt 0 ] && [ "$local_count" -lt "$remote_count" ]; then + echo "::warning title=zipx cache epoch::Local tags ($local_count) fewer than origin ($remote_count). Epoch may be stale; checkout must use fetch-depth: 0 and fetch-tags: true." + fi + fi + if [[ "${GITHUB_REF:-}" == refs/tags/v* ]]; then + release="${GITHUB_REF#refs/tags/v}" + epoch="$release" + elif tag=$(git describe --tags --abbrev=0 --match "$tag_match" 2>/dev/null); then + release="${tag#v}" + epoch="${release}-ci" + else + echo "::warning title=zipx cache epoch::No tags matching '$tag_match' found; using epoch 0.0.0." + release="0.0.0" + epoch="0.0.0" + fi + echo "epoch=$epoch" >> "$GITHUB_OUTPUT" + echo "release=$release" >> "$GITHUB_OUTPUT" + - name: Cache sbt uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0 with: @@ -125,11 +209,11 @@ jobs: ~/.cache/sbt ~/.cache/coursier target - key: ubuntu-latest-jdk25-sbt-0.0.0-ci-${{ github.run_id }}-publish + key: ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}-${{ github.run_id }}-publish restore-keys: | - ubuntu-latest-jdk25-sbt-0.0.0-ci-${{ github.run_id }}- - ubuntu-latest-jdk25-sbt-0.0.0-ci- - ubuntu-latest-jdk25-sbt-0.0.0- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}-${{ github.run_id }}- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.epoch }}- + ubuntu-latest-jdk25-sbt-${{ steps.cache-epoch.outputs.release }}- ubuntu-latest-jdk25-sbt- - name: publish run: sbt 'hubJS/publish; root/publish' diff --git a/build.sbt b/build.sbt index f7a4877..f747bde 100644 --- a/build.sbt +++ b/build.sbt @@ -1,8 +1,8 @@ val scala3Version = "3.8.4" val zioVersion = "2.1.26" val zioHttpVersion = "3.11.3" -val ascentVersion = "0.1.0" -val specularVersion = "0.9.0" +val ascentVersion = "0.3.1" +val specularVersion = "0.10.0" ThisBuild / scalaVersion := scala3Version ThisBuild / organization := "rocks.earlyeffect" diff --git a/project/plugins.sbt b/project/plugins.sbt index 1df0957..3ea3253 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -1,7 +1,4 @@ addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.22.0") -addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.1") +addSbtPlugin("org.scalameta" % "sbt-scalafmt" % "2.6.2") addSbtPlugin("rocks.earlyeffect" % "sbt-dynver-ci" % "0.2.2") -addSbtPlugin("rocks.earlyeffect" % "sbt-zipx" % "0.0.10") - -// zipx bundles sbt-remote-cache; compiler-interface is on both sbt-2.x and zinc-1.x schemes. -libraryDependencySchemes += "org.scala-sbt" % "compiler-interface" % "always" +addSbtPlugin("rocks.earlyeffect" % "sbt-zipx" % "0.0.12") diff --git a/src/main/scala/earlyeffect/hub/BuildHub.scala b/src/main/scala/earlyeffect/hub/BuildHub.scala index f3235ef..b5b010c 100644 --- a/src/main/scala/earlyeffect/hub/BuildHub.scala +++ b/src/main/scala/earlyeffect/hub/BuildHub.scala @@ -22,7 +22,7 @@ object BuildHub extends ZIOAppDefault: private val FallbackSpecular = ProjectMeta( name = "specular", organization = "rocks.earlyeffect", - version = "0.7.3", + version = "0.10.0", scalaVersion = "3.8.4", title = Some("Specular"), description = Some("Code-first tests-as-docs site generator for Scala."), @@ -92,9 +92,31 @@ object BuildHub extends ZIOAppDefault: _ <- EarlyEffectTheme.writeLogo(out) _ <- injectFavicon(out) _ <- Console.printLine(s"Wrote hub → $out (${result.pages.size} files)") - yield ()).provideLayer(Client.default ++ EarlyEffectTheme.layers) + yield ()).provideLayer(Client.default ++ hubLayers) end run + /** EE chalkboard theme: wider catalog + sticky landing footer (Specular 0.10.0 Landing is not yet flex). */ + private val hubLayers = + Theme.fromTokens( + EarlyEffectTheme.tokens.copy( + extraCss = EarlyEffectTheme.tokens.extraCss + + """ + |.specular-site-Theme-Landing { + | display: flex; + | flex-direction: column; + | min-height: 100vh; + |} + |.specular-site-Theme-Footer { + | margin-top: auto; + | flex-shrink: 0; + |} + |.specular-site-Theme-Catalog { + | max-width: min(72rem, calc(100% - 3rem)); + |} + |""".stripMargin + ) + ) >>> DocsSite.themedStack + private def copyStaticAssets(out: Path): Task[Unit] = ZIO.attempt { val srcDir = Paths.get("images") From e9ead1c9c9a5eab86d6d14e47c7c761f04460cfb Mon Sep 17 00:00:00 2001 From: Russ White <356303+russwyte@users.noreply.github.com> Date: Mon, 27 Jul 2026 19:57:54 -0500 Subject: [PATCH 2/2] Bump Specular to 0.10.1 and drop sticky-footer workaround. Landing footer pinning now comes from Specular; keep only the wider catalog override. --- build.sbt | 2 +- src/main/scala/earlyeffect/hub/BuildHub.scala | 13 ++----------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/build.sbt b/build.sbt index f747bde..49b2082 100644 --- a/build.sbt +++ b/build.sbt @@ -2,7 +2,7 @@ val scala3Version = "3.8.4" val zioVersion = "2.1.26" val zioHttpVersion = "3.11.3" val ascentVersion = "0.3.1" -val specularVersion = "0.10.0" +val specularVersion = "0.10.1" ThisBuild / scalaVersion := scala3Version ThisBuild / organization := "rocks.earlyeffect" diff --git a/src/main/scala/earlyeffect/hub/BuildHub.scala b/src/main/scala/earlyeffect/hub/BuildHub.scala index b5b010c..93d221f 100644 --- a/src/main/scala/earlyeffect/hub/BuildHub.scala +++ b/src/main/scala/earlyeffect/hub/BuildHub.scala @@ -22,7 +22,7 @@ object BuildHub extends ZIOAppDefault: private val FallbackSpecular = ProjectMeta( name = "specular", organization = "rocks.earlyeffect", - version = "0.10.0", + version = "0.10.1", scalaVersion = "3.8.4", title = Some("Specular"), description = Some("Code-first tests-as-docs site generator for Scala."), @@ -95,21 +95,12 @@ object BuildHub extends ZIOAppDefault: yield ()).provideLayer(Client.default ++ hubLayers) end run - /** EE chalkboard theme: wider catalog + sticky landing footer (Specular 0.10.0 Landing is not yet flex). */ + /** EE chalkboard theme with a wider catalog so the landing fills like project docs content. */ private val hubLayers = Theme.fromTokens( EarlyEffectTheme.tokens.copy( extraCss = EarlyEffectTheme.tokens.extraCss + """ - |.specular-site-Theme-Landing { - | display: flex; - | flex-direction: column; - | min-height: 100vh; - |} - |.specular-site-Theme-Footer { - | margin-top: auto; - | flex-shrink: 0; - |} |.specular-site-Theme-Catalog { | max-width: min(72rem, calc(100% - 3rem)); |}