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
58 changes: 58 additions & 0 deletions .changes/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Unreleased Changes

Drop one markdown file per change in this directory. At release time, `scripts/release.py` folds these into `CHANGELOG.md` and exports a `release.json` for the website.

## File format

```yaml
---
type: minor
---

Title line for the changelog entry

Optional body with more detail, bullet points, etc.
All of this goes into CHANGELOG.md.

- Detail one
- Detail two

## Summary

Short user-facing text for the website UI. Less technical.

## Notices

- warning: Users must re-pair their shockers after updating
- info: The captive portal now uses REST instead of WebSocket
- error: Third-party WS tools will break
```

### Fields

**type** (required): `major`, `minor`, or `patch`

**Changelog entry** (required): Everything between the frontmatter and the first `##` section. First line is the title, rest is the body. Both go into CHANGELOG.md.

**Summary** (optional): Short user-friendly text for the website/app UI.

**Notices** (optional): Structured list of `level: message` pairs. Valid levels: `info`, `warning`, `error`. These render as alert boxes in the website UI.

### Minimal example

```yaml
---
type: patch
---

Fix crash on knockoff boards after network connects
```

## Release workflow

```bash
python scripts/release.py status # See pending changes and next version
python scripts/release.py rc # Create or bump a release candidate tag
python scripts/release.py stable # Promote to stable, consume changes
python scripts/release.py --dry-run rc # Preview without making changes
```
12 changes: 12 additions & 0 deletions .changes/bug-fixes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
type: patch
---

Various bug fixes and stability improvements

- Cooperative task shutdown with bounded timeout and force-kill fallback
- User-friendly WiFi disconnect error messages
- Remove erroneous "unexpected protocol: https, expected http" warning

## Summary
Various bug fixes and stability improvements
13 changes: 13 additions & 0 deletions .changes/captive-portal-wizard.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
type: minor
---

Full setup wizard, REST API migration, and WiFi CRUD overhaul

- Migrate all LocalToHub WebSocket commands to HTTP REST endpoints
- Add guided setup wizard with multi-step stepper and advanced settings mode with vertical section navigation
- Portal close is now a soft signal that stays open until the device is fully online
- Add 5-minute auto-close timer when no WebSocket clients are connected

## Summary
New guided setup wizard walks you through device configuration step by step. An advanced settings mode is also available for experienced users. The captive portal now stays open until the device is fully connected, preventing premature disconnects during setup.
15 changes: 15 additions & 0 deletions .changes/frontend-svelte5.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
type: minor
---

Rewrite frontend to Svelte 5 + Vite

- Replace SvelteKit with plain Svelte 5 + Vite for the captive portal frontend
- Migrate to Svelte 5 runes ($state, $derived, $effect)
- Single-file HTML output via vite-plugin-singlefile for LittleFS

## Summary
The captive portal frontend has been completely rewritten using Svelte 5 with its new runes-based reactivity system, replacing the previous SvelteKit setup. The UI is now built as a single HTML file for efficient storage on the device.

## Notices
- info: The frontend was fully rewritten, but since it is served directly from the device's filesystem, no user action is required. The update is applied automatically with the firmware flash.
14 changes: 14 additions & 0 deletions .changes/visual-ledc-drivers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
type: minor
---

Replace LED managers with LEDC-driven drivers

- Rename PinPatternManager/RGBPatternManager to MonoLedDriver/RgbLedDriver
- Replace raw GPIO toggling with ESP32 LEDC hardware PWM for smooth 8-bit brightness control
- Use chip-agnostic LEDC speed mode (SOC_LEDC_SUPPORT_HS_MODE) for ESP32/S2/S3/C3 compatibility
- Add cooperative task shutdown with chunked 50ms delays
- Add ledtest serial command for visual verification

## Summary
LED indicators now use hardware PWM for smooth brightness transitions instead of simple on/off toggling. This applies to both single-color and RGB LEDs across all supported ESP32 chip variants.
8 changes: 8 additions & 0 deletions .changes/wellturn-t330.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
type: minor
---

Integrate remote WellturnT330 shocker support

## Summary
Added support for the Wellturn T330 shocker model.
5 changes: 0 additions & 5 deletions .changeset/mighty-pets-report.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/precious-eagle-cactus-fruit.md

This file was deleted.

5 changes: 0 additions & 5 deletions .changeset/smooth-leds-dance.md

This file was deleted.

33 changes: 20 additions & 13 deletions .github/scripts/get-vars.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,11 +111,12 @@ if (gitHeadRefName === 'master' || (isGitTag && isStableRelease(latestRelease)))
}

function getVersionChangeLog(lines) {
const isStableTag = isGitTag && isStableRelease(latestRelease);
const emptyChangelog = lines.length === 0;

// Enforce that the changelog is not empty if we are on the master branch
if (isGitTag && emptyChangelog) {
setFailed('File "CHANGELOG.md" is empty, this must be populated in the master branch');
// Only stable tags require changelog entries
if (isStableTag && emptyChangelog) {
setFailed('File "CHANGELOG.md" is empty, this must be populated for stable releases');
process.exit();
}

Expand All @@ -133,17 +134,22 @@ function getVersionChangeLog(lines) {

// Get the start of the entry
const changeLogBegin = lines.findIndex((line) => line.startsWith(`# Version ${currentVersion}`));
if (isGitTag && changeLogBegin === -1) {
if (isStableTag && changeLogBegin === -1) {
setFailed(
`File "CHANGELOG.md" does not contain a changelog entry for version "${currentVersion}", this must be added in the master branch`
`File "CHANGELOG.md" does not contain a changelog entry for version "${currentVersion}"`
);
process.exit();
}

// Enforce that the changelog entry is at the top of the file if we are on the master branch
if (isGitTag && changeLogBegin !== 0) {
// RC/beta/dev tags may not have a changelog entry - that's fine
if (changeLogBegin === -1) {
return '';
}

// Enforce that the changelog entry is at the top of the file for stable releases
if (isStableTag && changeLogBegin !== 0) {
setFailed(
`Changelog entry for version "${currentVersion}" is not at the top of the file, you tag is either out of date or you have not updated the changelog`
`Changelog entry for version "${currentVersion}" is not at the top of the file`
);
process.exit();
}
Expand All @@ -161,10 +167,9 @@ function getVersionChangeLog(lines) {
const emptyChangelogEntry =
lines.slice(changeLogBegin + 1, changeLogEnd).filter((line) => line.trim() !== '').length === 0;

// Enforce that the changelog entry is not empty if we are on the master branch
if (isGitTag && emptyChangelogEntry) {
if (isStableTag && emptyChangelogEntry) {
setFailed(
`Changelog entry for version "${currentVersion}" is empty, this must be populated in the master branch`
`Changelog entry for version "${currentVersion}" is empty`
);
process.exit();
}
Expand Down Expand Up @@ -195,15 +200,17 @@ const changelogVersions = fullChangelogLines
// Get the changelog for the current version
const versionChangeLog = getVersionChangeLog(fullChangelogLines);

// Enforce that all tags exist in the changelog
// Enforce that all stable tags exist in the changelog (RC/beta/dev tags are excluded)
let missingTags = [];
for (const tag of gitTagsArray) {
const parsed = semver.parse(tag);
if (parsed && parsed.prerelease.length > 0) continue; // Skip pre-release tags
if (!changelogVersions.includes(tag)) {
missingTags.push(tag);
}
}
if (missingTags.length > 0) {
setFailed(`Changelog is missing the following tags: ${missingTags.join(', ')}`);
setFailed(`Changelog is missing the following stable tags: ${missingTags.join(', ')}`);
process.exit();
}

Expand Down
36 changes: 36 additions & 0 deletions .github/workflows/check-changes.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
on:
pull_request:
branches:
- develop
types: [opened, reopened, synchronize, labeled, unlabeled]

name: check-changes

jobs:
require-changefile:
runs-on: ubuntu-latest
timeout-minutes: 1

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Check for change file
run: |
# PRs labeled "no-changelog" are exempt (dependency bumps, CI-only, etc.)
if echo '${{ toJSON(github.event.pull_request.labels.*.name) }}' | grep -q '"no-changelog"'; then
echo "::notice::Skipping changelog check (no-changelog label)"
exit 0
fi

# Check if any .changes/*.md files were added (excluding README.md)
CHANGES=$(git diff --name-only --diff-filter=A origin/${{ github.base_ref }}...HEAD -- '.changes/*.md' | grep -v README.md || true)

if [ -z "$CHANGES" ]; then
echo "::error::No change file found. Add a .changes/<name>.md file describing your change, or add the 'no-changelog' label to skip this check. See .changes/README.md for the format."
exit 1
fi

echo "Found change files:"
echo "$CHANGES"
2 changes: 0 additions & 2 deletions .github/workflows/ci-build.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
on:
push:
branches:
- master
- beta
- develop
tags:
- '[0-9]+.[0-9]+.[0-9]+'
Expand Down
51 changes: 51 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
on:
push:
branches:
- master
- beta

name: release

env:
PYTHON_VERSION: '3.13'

permissions:
contents: write

jobs:
auto-release:
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
# A PAT is required so the tag push triggers ci-build.
# The default GITHUB_TOKEN does not trigger downstream workflows.
token: ${{ secrets.RELEASE_TOKEN }}

- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}

- name: Configure git
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Create RC tag (beta)
if: github.ref == 'refs/heads/beta'
run: |
python scripts/release.py rc
TAG=$(git describe --tags --abbrev=0)
git push origin "$TAG"
echo "::notice::Created and pushed tag: $TAG"

- name: Create stable release (master)
if: github.ref == 'refs/heads/master'
run: |
python scripts/release.py stable
TAG=$(git describe --tags --abbrev=0)
git push origin HEAD "$TAG"
echo "::notice::Created and pushed tag: $TAG"
Loading
Loading