Skip to content
Open
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
57 changes: 26 additions & 31 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
name: Publish to pub.dev

# Four tag-gated jobs rather than one job listing all packages: the reusable
# publish workflow turns `packages:` into a parallel matrix with no ordering
# knob, and `remix_fortal` depends on hosted `remix` and `remix_ui_icons`
# versions, so a single job would race.
# Three tag-gated jobs keep each package aligned with its configured pub.dev
# automated-publishing tag pattern.
#
# THE FOUR PACKAGES PUBLISH FROM DIFFERENT TAG PATTERNS. This is not a style
# THE THREE PACKAGES PUBLISH FROM DIFFERENT TAG PATTERNS. This is not a style
# choice — it is each package's "Automated publishing" tag pattern on pub.dev,
# and the OIDC token is rejected if the pushed tag does not match:
#
# remix -> v<version> (bare, e.g. v1.0.0-beta.5)
# remix_fortal -> remix_fortal-v<version>
# remix_ui_icons -> remix_ui_icons-v<version>
# remix_cli -> remix_cli-v<version>
#
Expand All @@ -25,45 +22,42 @@ name: Publish to pub.dev
# publishes, `remix-v<version>` is what melos reads. Only the first triggers
# this workflow.
#
# The split does not order the two publishes: each tag push starts its own run,
# so `git push --tags` still races — and it can also fire this workflow from
# whatever stale `v*` tags a clone happens to hold. Push the tags one at a
# time, remix first, and wait for pub.dev to serve it before pushing the
# remix_fortal tag. pub.dev does not verify that a dependency version exists,
# so the wrong order ships an uninstallable remix_fortal rather than failing
# the job.
#
# remix_cli is versioned independently of the remix/remix_fortal pair, so its
# tag is pushed on its own schedule and orders against nothing. It depends on
# no other package here — it ships a CLI and a registry of templates — so it
# cannot race the jobs above.
# remix_cli and remix_ui_icons are versioned independently from Remix.
# The CLI consumer check requires its bundled Remix version to exist before
# the CLI can publish. remix_fortal is not publishable; its
# source remains in the repository as the Fortal preset's analyzed authoring
# and parity surface.
on:
push:
tags:
- 'remix_cli-v[0-9]+.[0-9]+.[0-9]+*'
- 'remix_fortal-v[0-9]+.[0-9]+.[0-9]+*'
- 'remix_ui_icons-v[0-9]+.[0-9]+.[0-9]+*'
- 'v[0-9]+.[0-9]+.[0-9]+*'

jobs:
publish-remix:
# Bare `v*` only. `refs/tags/remix_fortal-v...` does not start with
# `refs/tags/v`, so the two conditions stay mutually exclusive.
if: startsWith(github.ref, 'refs/tags/v')
uses: conceptadev/dart-actions/.github/workflows/publish.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
with:
packages_folder_path: "packages"
packages: "remix"
verify-cli-consumers:
if: startsWith(github.ref, 'refs/tags/remix_cli-v')
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: '3.44.0'
cache: true
- name: Resolve the workspace
run: flutter pub get
- name: Verify both presets with hosted Remix
run: dart run melos run open-code:release:check

publish-remix-fortal:
if: startsWith(github.ref, 'refs/tags/remix_fortal-v')
publish-remix:
# Bare `v*` only; package-prefixed tags do not start with `refs/tags/v`.
if: startsWith(github.ref, 'refs/tags/v')
uses: conceptadev/dart-actions/.github/workflows/publish.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
with:
packages_folder_path: "packages"
packages: "remix_fortal"
packages: "remix"
permissions:
id-token: write
contents: read
Expand All @@ -80,6 +74,7 @@ jobs:

publish-remix-cli:
if: startsWith(github.ref, 'refs/tags/remix_cli-v')
needs: verify-cli-consumers
uses: conceptadev/dart-actions/.github/workflows/publish.yml@9075ce1232ec77b8747953f2ff4a349190e5a805
with:
packages_folder_path: "packages"
Expand Down
92 changes: 44 additions & 48 deletions .github/workflows/version.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
name: Prepare Version Bump

# Pushes a branch that sets both published packages to one explicit version and
# writes their changelogs from conventional commits. It never tags and never
# publishes: `publish.yml` fires on a tag push, so the tag is pushed by hand
# after this PR is reviewed and merged.
#
# The version is a single input rather than one per package. remix and
# remix_fortal ship as a pair and must carry the identical version, so there is
# no per-package value to disagree about.
# Pushes a branch that sets Remix to one explicit version and writes its
# changelog from conventional commits. It never tags and never publishes:
# `publish.yml` fires on a tag push, so the tag is pushed by hand after this PR
# is reviewed and merged. remix_fortal is an unpublished authoring package and
# is deliberately outside this workflow.
#
# It pushes a branch and prints its compare link rather than opening the pull
# request itself. Opening one from a workflow needs "Allow GitHub Actions to
Expand All @@ -18,21 +15,21 @@ on:
workflow_dispatch:
inputs:
version:
description: "Exact version for BOTH remix and remix_fortal (e.g. 1.0.0-beta.5)."
description: "Exact version for remix (e.g. 1.0.0-beta.9)."
required: true

permissions:
contents: write

jobs:
version:
name: Bump versions and changelogs
name: Bump Remix version and changelog
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# melos derives each package's last release from its `<package>-v*`
# tag, so the full history and all tags must be present. A shallow
# melos derives Remix's last release from its `remix-v*` tag, so the
# full history and all tags must be present. A shallow
# clone makes it walk every commit and rewrite the whole changelog.
fetch-depth: 0

Expand All @@ -56,15 +53,6 @@ jobs:

version='${{ inputs.version }}'

# One input applied to both packages, rather than a version per
# package that a human has to keep in step. remix and remix_fortal
# release in lockstep (tool/check_version_alignment.dart enforces it
# in CI), and melos has no lockstep mode of its own — left to derive
# versions from commits it would bump each package independently and
# split the pair apart. Passing an explicit `-V` for both is what
# keeps them identical.
overrides="-V remix:$version -V remix_fortal:$version"

# No `dart pub global activate melos`. melos runs whichever version
# the workspace pins as a dev dependency, so a global install cannot
# decide the version and only prints a reassuring, wrong one into the
Expand Down Expand Up @@ -95,30 +83,39 @@ jobs:
# is not what makes this work: melos re-executes itself and the
# child does not inherit the redirect, so a redirected stdin alone
# still reached the prompt and threw StdinException.
# Scope is explicit so a conventional commit touching remix_cli
# cannot pull that independently versioned package into the lockstep
# Remix/Fortal release branch.
# `-V` makes the workflow input authoritative rather than asking
# melos to infer a version from commits. Scope is explicit so
# independently versioned remix_cli cannot enter the release branch.
# Disable dependent constraint and version propagation too: melos
# otherwise rewrites and can version workspace dependents even when
# the initial package filter selects only Remix, which would pull the
# unpublished remix_fortal authoring package back into releases.
dart run melos version \
--scope=remix \
--scope=remix_fortal \
--no-dependent-constraints \
--no-dependent-versions \
--no-git-commit-version \
--yes \
$overrides < /dev/null
-V "remix:$version" < /dev/null

# melos rewrites the `remix` constraint of every pubspec dependent,
# which is how remix_fortal's pin follows the bump. remix_cli's
# registry.yaml is data, not a pubspec, so melos never reaches it and
# its `remix: ^<floor>` would keep pointing at the previous release.
# remix_cli's registry.yaml is data, not a pubspec, so melos never
# reaches it and its `remix: ^<floor>` would keep pointing at the
# previous release.
# That floor is what the CLI reports as the tested version, so this
# step is part of versioning, not a cleanup after it.
dart run tool/sync_registry_remix.dart

# The pair must land aligned; verify rather than trust the inputs.
# This also fails when the sync step above did not run.
# The Fortal preset copies its dependency floors from the default
# registry. Rebuild it immediately after the default floor moves so
# the two committed registry trees cannot disagree.
dart run tool/build_fortal_preset.dart

# Verify the released Remix version and registry floor agree. This
# also fails when the sync step above did not run.
dart run tool/check_version_alignment.dart

if git diff --quiet && git diff --cached --quiet; then
echo "No packages required versioning."
echo "Remix did not require versioning."
echo "versioned=false" >> "$GITHUB_OUTPUT"
exit 0
fi
Expand All @@ -128,39 +125,38 @@ jobs:
git checkout -b release/version-packages

git add -A
git commit -m "chore(release): version packages"
git commit -m "chore(release): version Remix"
git push --set-upstream origin release/version-packages --force

- name: Summarise
if: steps.bump.outputs.versioned == 'true'
run: |
url="${{ github.server_url }}/${{ github.repository }}/compare/main...release/version-packages?expand=1"
# GitHub expands these workflow expressions before the shell sees
# the intentionally single-quoted Markdown commands below.
# shellcheck disable=SC2016
{
echo "### Versions bumped"
echo "### Remix version bumped"
echo
echo "[Open the pull request]($url)"
echo
echo "Both packages are on \`${{ inputs.version }}\`."
echo "Remix is on \`${{ inputs.version }}\`."
echo
echo "After merging, push these tags ONE AT A TIME, in this order."
echo "After merging, push these Remix tags one at a time."
echo "Do not \`git push --tags\`: stale \`v*\` tags in this repo"
echo "would start publish runs from the wrong commits."
echo
echo '1. `v${{ inputs.version }}` — publishes **remix**. Wait until'
echo ' pub.dev actually serves it before step 2; pub.dev does not'
echo ' verify a dependency exists, so publishing remix_fortal'
echo ' early ships an uninstallable package rather than failing.'
echo '2. `remix_fortal-v${{ inputs.version }}` — publishes **remix_fortal**.'
echo '3. `remix-v${{ inputs.version }}` — publishes nothing. melos'
echo '1. `v${{ inputs.version }}` — publishes **remix**.'
echo '2. `remix-v${{ inputs.version }}` — publishes nothing. melos'
echo ' reads it to derive the next release changelog.'
echo
echo "### CI on this branch"
echo
echo 'This branch also moves the `remix` floor in the'
echo '`remix_cli` registry. `open-code:check` resolves that floor'
echo 'from pub.dev, so it fails until step 1 above is served.'
echo 'Expect a red run before the tag and a green one after; do'
echo 'not lower the floor to make it pass.'
echo 'CI installs both presets against checkout Remix.'
echo 'After pub.dev serves step 1, run'
echo '`dart run melos run open-code:release:check`.'
echo 'The CLI publish workflow requires this hosted check.'
echo 'Do not lower the registry floor to bypass a missing release.'
echo
echo "### remix_cli"
echo
Expand Down
17 changes: 12 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,17 @@ final destructiveButton = baseButtonStyle
## Theming with Fortal

Remix ships no theme of its own. If you want a polished, Radix Themes-inspired
starting point instead of authoring every style yourself, add the companion
[`remix_fortal`](https://pub.dev/packages/remix_fortal) package — it provides
`FortalScope`, a token system, the `fortal*Style()` recipes, and a matching
catalog of ready-made `Fortal*` widgets.
starting point instead of authoring every style yourself, initialize the
application-owned Fortal preset:

```bash
flutter pub add dev:remix_cli
dart run remix_cli:remix init --prefix Fortal --preset fortal
dart run remix_cli:remix add button
```

It copies `FortalScope`, the token system, `fortal*Style()` recipes, and the
components you add into your application.

See the [Fortal documentation](https://docs.page/btwld/remix/fortal) to get
started.
Expand All @@ -233,7 +240,7 @@ Remix composes inside your existing Flutter host. It does not require a
| UI | Caller provides | Compatible hosts |
|----|-----------------|------------------|
| Ordinary `Remix*` widgets | The inherited Flutter services used by the widget subtree | Material, Cupertino, Widgets, and router-based hosts |
| Widgets styled by a theme package such as Fortal | That package's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
| Widgets styled by the Fortal preset | The preset's token scope, in addition to the widget's normal Flutter services | Any Flutter host |
| Menu, select, popover, and tooltip | An `Overlay` | Any host exposing an overlay; use `Overlay.wrap` when no `Navigator` is needed |
| `showRemixDialog` and `showRemixAlertDialog` | A `Navigator` | Any host with a caller-owned navigator |

Expand Down
15 changes: 8 additions & 7 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
# `analysis_options.yaml`, and this file must not shadow them. It exists for
# one reason.
#
# `open_code/fixture/` is a standalone consumer application, not a workspace
# member (see open_code/README.md). It resolves its dependencies from pub.dev
# inside the throwaway app that `tool/check_open_code.dart` assembles, so it
# has no `.dart_tool/package_config.json` here and its `package:` imports
# cannot resolve in this checkout. Its own pubspec makes it a nested analysis
# context, so a root-level `dart analyze` would descend into it and report
# every import as missing.
# The `open_code/*fixture/` directories are standalone consumer applications,
# not workspace members (see open_code/README.md). They resolve dependencies
# from pub.dev inside the throwaway apps that `tool/check_open_code.dart`
# assembles, so they have no `.dart_tool/package_config.json` here and their
# `package:` imports cannot resolve in this checkout. Their own pubspecs make
# them nested analysis contexts, so a root-level `dart analyze` would descend
# into them and report every import as missing.
#
# Excluding it does not weaken coverage: the checker runs `flutter analyze`
# over that exact source in the assembled app, where its dependencies are real.
Expand All @@ -20,3 +20,4 @@
analyzer:
exclude:
- open_code/fixture/**
- open_code/fortal_fixture/**
6 changes: 3 additions & 3 deletions docs/components/accordion.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,13 @@ class _AccordionExampleState extends State<AccordionExample> {

## Fortal widgets

Remix includes a Fortal-themed widget for this component:
The application-owned Fortal preset provides a themed widget for this component:

<CodeGroup title="Fortal widget" defaultLanguage="dart">
```dart
import 'package:flutter/material.dart';
import 'package:remix/remix.dart';
import 'package:remix_fortal/remix_fortal.dart';
import 'ui/ui.dart';

class FortalAccordionExample extends StatefulWidget {
const FortalAccordionExample({super.key});
Expand Down Expand Up @@ -200,7 +200,7 @@ class _FortalAccordionExampleState extends State<FortalAccordionExample> {
</CodeGroup>

<Info>
See the [fortalAccordionStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/accordion.dart) for all available options.
See the [fortalAccordionStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/accordion.dart) for all available options.
</Info>

## Constructor
Expand Down
6 changes: 3 additions & 3 deletions docs/components/avatar.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ class AvatarExample extends StatelessWidget {

## Fortal widgets

Remix includes a Fortal-themed widget for this component:
The application-owned Fortal preset provides a themed widget for this component:

<CodeGroup title="Fortal variants" defaultLanguage="dart">
```dart
import 'package:flutter/material.dart';
import 'package:remix_fortal/remix_fortal.dart';
import 'ui/ui.dart';

class FortalAvatarExample extends StatelessWidget {
const FortalAvatarExample({super.key});
Expand Down Expand Up @@ -111,7 +111,7 @@ class FortalAvatarExample extends StatelessWidget {

<Info>
Set `fallbackLength: 2` for two-character labels so the generated widget uses
the pinned two-initial typography. See the [fortalAvatarStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/avatar.dart) for all available options.
the pinned two-initial typography. See the [fortalAvatarStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/avatar.dart) for all available options.
</Info>

## Constructor
Expand Down
6 changes: 3 additions & 3 deletions docs/components/badge.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ class BadgeExample extends StatelessWidget {

## Fortal widgets

Remix includes a Fortal-themed widget for this component:
The application-owned Fortal preset provides a themed widget for this component:

<CodeGroup title="Fortal variants" defaultLanguage="dart">
```dart
import 'package:flutter/material.dart';
import 'package:remix_fortal/remix_fortal.dart';
import 'ui/ui.dart';

class FortalBadgeExample extends StatelessWidget {
const FortalBadgeExample({super.key});
Expand Down Expand Up @@ -111,7 +111,7 @@ class FortalBadgeExample extends StatelessWidget {
</CodeGroup>

<Info>
See the [fortalBadgeStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/recipes/badge.dart) for all available options.
See the [fortalBadgeStyle source code](https://github.com/btwld/remix/blob/main/packages/remix_fortal/lib/src/components/badge.dart) for all available options.
</Info>

<Warning>
Expand Down
Loading
Loading