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
91 changes: 91 additions & 0 deletions .github/workflows/test-build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Manually builds a downloadable Windows test package without creating a tag,
# GitHub Release or updater signature. Intended for verifying feature branches
# and unreleased changes before the real signed release pipeline is used.
name: Test Build

on:
workflow_dispatch:

permissions:
contents: read

concurrency:
group: test-build-${{ github.ref }}
cancel-in-progress: true

jobs:
windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v6

- uses: oven-sh/setup-bun@v2

- uses: actions/setup-dotnet@v5
with:
dotnet-version: 8.0.x

- uses: dtolnay/rust-toolchain@stable

- uses: Swatinem/rust-cache@v2
with:
workspaces: src-tauri

- name: Install dependencies
run: bun install --frozen-lockfile

- name: Realtime collaboration selftest
run: bun run selftest:live

- name: Project schema selftest
run: bun run selftest:project

- name: Publish sidecar
shell: pwsh
run: ./sidecar/publish.ps1

- name: Build unsigned test installers
shell: pwsh
run: |
# A test build does not feed the public auto-updater, so updater
# artifacts/signatures are intentionally disabled for this run.
$testConfig = Join-Path $env:RUNNER_TEMP "tauri.test-build.json"
'{"bundle":{"createUpdaterArtifacts":false}}' |
Set-Content -LiteralPath $testConfig -Encoding utf8
bun run tauri build --ci --no-sign --config $testConfig

- name: Assemble portable test ZIP
shell: pwsh
run: |
$releaseDir = "src-tauri/target/release"
$portableDir = "dist-test/atelier by feelgood"
New-Item -ItemType Directory -Force $portableDir | Out-Null
$main = @("$releaseDir/atelier by feelgood.exe", "$releaseDir/atelier.exe") |
Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $main) { throw "Main executable not found under $releaseDir" }
Copy-Item $main "$portableDir/atelier by feelgood.exe"
Copy-Item `
"src-tauri/binaries/fg-atelier-sidecar-x86_64-pc-windows-msvc.exe" `
"$portableDir/fg-atelier-sidecar.exe"
if (Test-Path "$releaseDir/WebView2Loader.dll") {
Copy-Item "$releaseDir/WebView2Loader.dll" $portableDir
}
@(
"atelier by feelgood – unsigned test build",
"",
"Start 'atelier by feelgood.exe'. Keep fg-atelier-sidecar.exe in the same folder.",
"This package is for testing only and does not use the automatic updater."
) -join "`r`n" | Set-Content "$portableDir/README.txt" -Encoding utf8
Compress-Archive -Path $portableDir -DestinationPath "atelier-test-portable.zip" -Force

- name: Upload test packages
uses: actions/upload-artifact@v4
with:
name: atelier-windows-test-${{ github.run_number }}
if-no-files-found: error
retention-days: 14
compression-level: 0
path: |
src-tauri/target/release/bundle/nsis/*-setup.exe
src-tauri/target/release/bundle/msi/*.msi
atelier-test-portable.zip
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,28 @@ All notable changes to **atelier** are documented here.
The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project follows [Semantic Versioning](https://semver.org/).

## [Unreleased]

### Added

- **Realtime team workspace.** Linked projects now publish stable-id,
field-level operations automatically and apply accepted WebSocket operations
immediately on every connected client. Adds full project coverage (groups,
drawables, tattoos, ordering, deletes and asset changes), crash-safe local
operation queuing, reconnect catch-up and exact content-addressed binary
transfer. `pack.atelier` format v3 stores the live workspace cursor.

### Fixed

- Transient workspace contention, rate limits and missing CAS uploads now keep
their durable operation queued instead of discarding a valid local change.
- Startup/reconnect broadcasts can no longer fall into the gap before the
first HTTP snapshot, and edits made while remote assets download are
re-overlaid before the project store is replaced.
- Full authoritative sync verifies local asset size and SHA-256, preventing an
externally changed or partially optimized texture from masquerading as the
cloud version.

## [1.10.0] — 2026-07-21

### Added
Expand Down
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,12 @@ optionally collaborate as a team over a cloud.
your server and let you browse it on a ped — with the labels and groups you
gave your items, instead of bare index numbers. Off by default; without the
tick the build output is unchanged and the pack stays invisible to the viewer.
- **Team cloud** _(optional)_ — push/pull against versioned pack revisions, live
presence and advisory locks (via [atelier-api](https://github.com/feelgoodrp-com/atelier-api)).
- **Team cloud** _(optional)_ — Google-Docs-style live project operations:
names, settings, groups, clothing, tattoos, ordering, deletes and optimized
asset hashes appear automatically on every connected client. Binary assets
are uploaded content-addressed before the operation is published; reconnects
recover from the durable server workspace and local operation queue (via
[atelier-api](https://github.com/feelgoodrp-com/atelier-api)).
- **Import wizard** — existing packs as well as `.ydd`/`.ytd`/`.yld` via drag &
drop, with automatic classification.

Expand Down Expand Up @@ -91,6 +95,7 @@ Useful scripts:
```powershell
bun run build # frontend typecheck + Vite build
bun run selftest:project # project-format / sync self-test
bun run selftest:live # realtime operation-diff self-test
bun run sidecar:publish # builds the sidecar (src-tauri/binaries/…)
bun run tauri:build # release bundle (installer + portable)
```
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "atelier",
"private": true,
"version": "1.10.0",
"version": "1.11.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -12,6 +12,7 @@
"tauri:build": "bun run sidecar:publish && tauri build",
"sidecar:publish": "powershell -NoProfile -ExecutionPolicy Bypass -File sidecar/publish.ps1",
"selftest:project": "bun run src/lib/project/__selftest__.ts",
"selftest:live": "bun run src/lib/sync/__live-selftest__.ts",
"selftest:menyoo": "bun run src/lib/preview/__selftest__.ts",
"selftest:logs": "bun run src/lib/log-humanize.selftest.ts"
},
Expand Down
4 changes: 2 additions & 2 deletions sidecar/Api/BuildEndpoints.cs
Original file line number Diff line number Diff line change
Expand Up @@ -455,9 +455,9 @@ private static void CollectEntries(RpfFile rpf, List<string> entries)
return Results.BadRequest(new ErrorResponse($"Projektordner nicht gefunden: {projectDir}"));
if (project == null)
return Results.BadRequest(new ErrorResponse("Feld 'project' fehlt."));
if (project.Fgcloth is not (1 or 2))
if (project.Fgcloth is not (1 or 2 or 3))
return Results.BadRequest(new ErrorResponse(
$"Nicht unterstützte Projektversion (fgcloth={project.Fgcloth}, erwartet 1 oder 2)."));
$"Nicht unterstützte Projektversion (fgcloth={project.Fgcloth}, erwartet 1, 2 oder 3)."));
return null;
}
}
2 changes: 1 addition & 1 deletion sidecar/Engine/Build/ProjectModel.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Feelgood.Atelier.Sidecar.Engine.Build;

/// <summary>
/// C# mirror of the `pack.atelier` project file (fgcloth v1, see
/// C# mirror of the `pack.atelier` project file (fgcloth v1-v3, see
/// atelier/src/lib/project/schema.ts). Only build-relevant fields are mapped;
/// unknown JSON properties are ignored by the (case-insensitive) binder.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion sidecar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ Body:

```json
{
"projectDir": "C:\\…", "project": { "fgcloth": 1, … },
"projectDir": "C:\\…", "project": { "fgcloth": 3, … },
"target": "fivem" | "singleplayer" | "ragemp" | "altv",
"outDir": "C:\\out",
"options": { "dlcName": "mypack", "resourceName": null, "generateShopMeta": true, "splitAt": 128 }
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "atelier"
version = "1.10.0"
version = "1.11.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "atelier by feelgood",
"version": "1.10.0",
"version": "1.11.0",
"identifier": "com.feelgood.atelier",
"build": {
"beforeDevCommand": "bun run dev",
Expand Down
4 changes: 3 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { getLogConsoleEnabled, getOnboardingDone } from "@/lib/settings";
import { openLogWindow, useLogConsoleStore } from "@/lib/stores/log-console-store";
import { usePresenceHeartbeat } from "@/lib/sync/presence";
import { useCollab } from "@/lib/sync/collab";
import { useLiveWorkspace } from "@/lib/sync/live";
import { startAutosave } from "@/lib/project/autosave";
import { useUpdateStore } from "@/lib/stores/update-store";
import { loadPreferences, usePreferencesStore } from "@/lib/stores/preferences-store";
Expand Down Expand Up @@ -52,8 +53,9 @@ function App() {
// "Wer ist online?" heartbeat (active only when logged in + approved).
usePresenceHeartbeat();

// Pack-room WebSocket + advisory edit locks (cloud-linked projects only).
// Pack-room WebSocket + enforced edit locks (cloud-linked projects only).
useCollab();
useLiveWorkspace();

// Restore settings + silent login on startup.
useEffect(() => {
Expand Down
Loading
Loading