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
389 changes: 389 additions & 0 deletions .planning/handovers/4.3.0_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,389 @@
# tsParticles 4.3.0 — Master Coordination Plan

## Overview

This plan coordinates **3 independent features** targeting the `4.3.0` release.
Each feature has its own detailed handover plan. This document manages sequencing, dependencies,
conflict areas, and release readiness.

---

## Hard Rule: Master Plan First, Feature Plans After

All validation steps defined in **this** document (see "Step 0: Baseline Validation" below) **must be executed and passing before any feature-specific plan** (i.e. before `BACKGROUND_CANVAS_PLAN.md`, `FLUID_INTERACTION_PLAN.md`, `GIF_SHAPE_PLAN.md`) is worked on. Even if new feature plans are added to the 4.3.0 scope mid-cycle, run this master plan's checks first, then proceed to the feature plan's steps.

This rule exists because the 4.2.0 release was burned by a `workspace:*` → hardcoded version regression discovered only at CI publish time. Running the master gate first establishes a baseline so feature work doesn't mask or introduce regressions.

---

## Step 0: Baseline Validation (run BEFORE any feature work)

Before touching any feature plan, execute these checks on the current `main` state:

### 0.1 — Workspace `@tsparticles/` deps use `workspace:*`

Run from repo root:
```bash
node -e "
const glob = require('fs').readdirSync;
const dirs = [...require('fs').readFileSync('pnpm-workspace.yaml','utf8').matchAll(/^ - (.+)/gm)].map(m=>m[1]);
const pkgs = dirs.flatMap(d => glob(d, {withFileTypes:true}).filter(e=>e.isDirectory()).map(e=>e.parentPath+'/'+e.name+'/package.json'));
pkgs.push('engine/package.json');
let fail = false;
for (const f of pkgs) {
try {
const p = require(f);
const deps = {...p.dependencies, ...p.devDependencies};
for (const [k,v] of Object.entries(deps))
if (k.startsWith('@tsparticles/') && v !== 'workspace:*')
{ console.log('❌', f, ':', k, v); fail = true; }
} catch { /* skip non-existent */ }
}
if (!fail) console.log('✅ all workspace:*');
"
```
Known root cause: version-bump scripts (e.g. `lerna version`, `nx release version`) sometimes rewrite `workspace:*` → hardcoded version in one or more `package.json` files. In the 4.2.0 release, `cli/utils/eslint-config/package.json` had `@tsparticles/prettier-config` changed to `"4.2.0"` while the lockfile still expected `workspace:*`.

### 0.2 — `pnpm run build:ci` passes on clean state

```bash
pnpm run build:ci
```

If either check fails, fix the regression first before starting any feature work. Log the fix commit hash in the dashboard.

---

## Progress Dashboard

Checklist riassuntiva di tutte le attività del piano. Da tenere aggiornata durante l'esecuzione.

```
## 4.3.0 Dashboard — [date]

### A: Background Canvas
- [ ] Phase 0: hook points identified
- [ ] Phase 1: size delta measured, path chosen
- [ ] Phase 1b: release target decided
- [ ] Phase 2: API/types exported
- [ ] Phase 3: frame hook integrated
- [ ] Phase 4: context cache + fallback integrated
- [ ] Phase 5: warning/error policy integrated
- [ ] Phase 6: tests added and passing
- [ ] Phase 7: docs/examples updated
- [ ] Phase 8: validation commands passing

### B: Fluid Interaction
- [ ] Step 0: conventions aligned, naming frozen
- [ ] Step 1: package skeleton + noop interactor
- [ ] Step 2: options contract + loaders + tests
- [ ] Step 3: baseline DDR solver
- [ ] Step 4: boundaries + caps + stability
- [ ] Step 5: viscosity + pointer stirring
- [ ] Step 6: interoperability validated
- [ ] Step 7: performance + soak passed
- [ ] Step 8: docs + presets finalized
- [ ] Step 9: (optional) plugin orchestration

### C: GIF Shape
- [ ] Step 1: scaffold shapes/gif/ (17 files)
- [ ] Step 2: copy + adapt GifUtils (10 files)
- [ ] Step 3: remove GIF from shape-image (9 files)
- [ ] Step 4: add to @tsparticles/all (2 files)
- [ ] Step 5: build + verify
- [ ] Step 6: demo config updated

### Release Gate
- [ ] Step 0.1 — Workspace `@tsparticles/` deps use `workspace:*`
- [ ] Step 0.2 — `pnpm run build:ci` passes
- [ ] Root `package.json` version matches packages (sincronizzazione automatica via script, non manuale)
- [ ] Integration tests pass
- [ ] Demo server smoke-tested
```

---

## Feature Inventory

| # | Feature | Package(s) Affected | Type | Plan |
|---|---------|--------------------|------|------|
| A | Background Canvas programmable hook | `@tsparticles/engine` | Engine enhancement | `BACKGROUND_CANVAS_PLAN.md` |
| B | Fluid Particle Interaction | `@tsparticles/interaction-particles-fluid` | New package | `FLUID_INTERACTION_PLAN.md` |
| C | Separate GIF Shape from Image Shape | `@tsparticles/shape-gif` (new), `@tsparticles/shape-image` (modified), `@tsparticles/all` (modified) | Refactor + new package | `GIF_SHAPE_PLAN.md` |

---

## Dependency Graph

```
A (Background Canvas) B (Fluid Interaction) C (GIF Shape)
engine only new package only shape-image refactor
no deps on B/C no deps on A/C shape-gif (new)
all bundle update
no deps on A/B

All three are INDEPENDENT — no shared files, no conflicting changes.
They can be implemented in any order or in parallel.
```

### Conflict matrix

| Area | A ∩ B | A ∩ C | B ∩ C |
|------|-------|-------|-------|
| Files | None | None | None |
| Types | None | None | None |
| Engine | A modifies engine; B, C only use engine types | Same | None |
| Bundle `all` | Not affected | C touches `bundles/all/` | Not affected |
| Risk | None isolated | None isolated | None isolated |

**Verdict:** All three are fully parallelizable. No merge conflicts expected.
The only integration point is `@tsparticles/all` (feature C adds a dependency).

---

## Sequencing Strategy

Since all features are independent, the recommended execution is **parallel**:

```
Week 1-2:
Agent A: Background Canvas (Phases 0-4)
Agent B: Fluid Interaction (Steps 0-5)
Agent C: GIF Shape (Steps 1-4)

Week 2-3:
Agent A: Background Canvas (Phases 5-8)
Agent B: Fluid Interaction (Steps 6-9)
Agent C: GIF Shape (Steps 5-6)

Week 3-4:
Integration: build all, integration tests, soak tests
Release prep: changelog, docs, version bump to 4.3.0
```

If resources are limited, priority order:
1. **Feature C (GIF Shape)** — simplest, lowest risk, unblocks the image shape cleanup
2. **Feature A (Background Canvas)** — moderate, needs bundle-size measurement gate
3. **Feature B (Fluid Interaction)** — most complex, new simulation code, needs tuning

---

## Cross-feature Concerns

### Per-Feature Verification (run during/after each feature implementation)

- [ ] **A:** Bundle impact decision recorded (engine vs plugin path) and within accepted threshold
- [ ] **A:** Zero regressions on legacy background behavior
- [ ] **B:** No NaN/Infinity in standard presets after 20k+ frame soak
- [ ] **B:** Fluid visual effect clearly distinguishable from plain attract/repulse
- [ ] **C:** `@tsparticles/shape-image` builds with zero GIF references
- [ ] **C:** `@tsparticles/shape-gif` builds and registers as `type: "gif"`
- [ ] **C:** GIF demo config (`utils/configs/src/g/gifs.ts`) works with new shape

### Pre-Release Gate (run before cutting release branch)

- [ ] **ALL:** `pnpm run build:ci` passes
- [ ] **ALL:** No high-severity open issues targeting 4.3.0
- [ ] **ALL:** Workspace `@tsparticles/` deps use `workspace:*` (re-run Step 0.1 check)
- [ ] **ALL:** Root `package.json` version matches packages — implementata una sincronizzazione automatica (es. script `scripts/sync-root-version.js` chiamato da `version` lifecycle hook o integrato nel comando di version bump)
- [ ] **ALL:** Integration tests pass (see below)
- [ ] **ALL:** Demo server smoke-tested (see below)
- [ ] **ALL:** Integration tests pass (see below)
- [ ] **ALL:** Demo server smoke-tested (see below)

### Integration testing

After individual feature verification, run combined scenarios:

```bash
pnpm run build:ci
pnpm exec vitest run
```

Then smoke-test the demo server:

```bash
cd demo/vanilla && pnpm start
```

### Changelog entries (draft)

Each feature produces its own changelog section:

```
## 4.3.0

### New Features
- Background Canvas: programmable `background.draw` callback + `background.element` target (#A)
- Fluid Interaction: new `@tsparticles/interaction-particles-fluid` package with DDR-based
liquid simulation, pointer stirring, and presets (#B)
- GIF Shape: new `@tsparticles/shape-gif` shape extracted from image shape.
Use `type: "gif"` instead of `type: "image"` with `gif: true` (#C)

### Breaking Changes
- `@tsparticles/shape-image`: removed animated GIF support (`gif` option no longer available).
Migrate to `@tsparticles/shape-gif` with `type: "gif"`.

### Bundle Changes
- `@tsparticles/all`: now includes `@tsparticles/shape-gif`
```

---

## Feature A — Background Canvas (from `BACKGROUND_CANVAS_PLAN.md`)

**Target:** `@tsparticles/engine`
**Load function:** N/A (built into engine)
**Plan phases:** 0-8

### Key decisions (from plan)

1. **Bundle impact gate** (Phase 1): measure delta; if LOW/MEDIUM → keep in engine; if HIGH → extract plugin
2. **Release target gate** (Phase 1b): feature-gate passes → target 4.3.0

### Implementation phases

| Phase | Description | Verification |
|-------|-------------|-------------|
| 0 | Baseline scan — identify insertion points | Short notes |
| 1 | Bundle impact measurement + decision | Size delta block |
| 1b | Release target decision | Go/No-go for 4.3.0 |
| 2 | Type contract — add `element`, `draw`, `BackgroundDrawContext` | Compile passes |
| 3 | Runtime hook in `RenderManager.drawParticles` after `clear()` | Callback runs in order |
| 4 | Target context resolution + caching | Deterministic selection |
| 5 | Error/warning handling (once-per-key, try/catch) | No log flood |
| 6 | Tests — load, precedence, fallback, resilience | Tests green |
| 7 | Docs/examples | No ambiguity |
| 8 | Validation gate | Final checklist complete |

### Critical files

| File | Change |
|------|--------|
| `engine/src/Options/Interfaces/Background/IBackground.ts` | Add `element`, `draw` |
| `engine/src/Options/Classes/Background/Background.ts` | Add properties + load |
| `engine/src/Core/Utils/RenderManager.ts` | Add callback call after `clear()` |
| `engine/src/export-types.ts` | Export `BackgroundDrawContext` |

---

## Feature B — Fluid Interaction (from `FLUID_INTERACTION_PLAN.md`)

**Target:** `@tsparticles/interaction-particles-fluid`
**Load function:** `loadParticlesFluidInteraction(engine)`
**Plan steps:** 0-9

### Execution governance

- Agent must NOT create commits or PRs
- Agent must leave changes uncommitted for maintainer review

### Implementation steps

| Step | Description | Verification |
|------|-------------|-------------|
| 0 | Repository alignment — inspect conventions, freeze naming | Checklist |
| 1 | Package skeleton — scaffold + noop `FluidInteractor` | Build passes |
| 2 | Options contract — `IFluid` + classes + loaders + unit tests | Tests pass |
| 3 | Baseline DDR solver — density, pressure, displacement, velocity reconstruction | Visual blob, no NaN |
| 4 | Constraints — boundary clamp/bounce, maxNeighbors, stability | Stable at edges |
| 5 | Viscosity + pointer stirring | Local deformation + recovery |
| 6 | Interoperability — validate with move/emitters/repulse/collisions | Matrix validated |
| 7 | Performance + soak — benchmark 300/600/1000 particles | Meets thresholds |
| 8 | Docs + presets — README, 3 presets, changelog | No undocumented options |
| 9 | Optional advanced path (plugin orchestration) — only if ordering artifacts | Before/after evidence |

### Critical options (defaults)

```json
{
"particles": {
"fluid": {
"enable": false,
"radius": 34, "stiffness": 0.45, "nearStiffness": 0.8,
"restDensity": 3, "viscosity": 0.08, "iterations": 2,
"maxForce": 2.5, "maxNeighbors": 64,
"boundaryBounce": 0.2, "boundaryClamp": true,
"gravity": { "enable": true, "acceleration": 0.01 },
"pointer": { "enable": false, "radius": 90, "strength": 0.45, "mode": "push" }
}
}
}
```

---

## Feature C — GIF Shape (from `GIF_SHAPE_PLAN.md`)

**Target:** `@tsparticles/shape-gif` (new), `@tsparticles/shape-image` (modified), `@tsparticles/all` (modified)
**Load function:** `loadGifShape(engine)`
**Plan substeps:** 1.1 through 6.1

### Implementation steps

| Step | Description | Verification |
|------|-------------|-------------|
| 1 | Scaffold `shapes/gif/` — 17 new files | Directory exists |
| 2 | Copy `GifUtils/` from image shape — adapt `drawGif`, remove `loadGifImage` | Imports resolve |
| 3 | Remove GIF from `@tsparticles/shape-image` — 9 files modified | `grep` zero matches |
| 4 | Add `@tsparticles/shape-gif` to `@tsparticles/all` bundle | Build passes |
| 5 | Build + verify — `pnpm run build:ci` | `dist/` output OK |
| 6 | Update demo config `utils/configs/src/g/gifs.ts` | Demo works |

### Critical files

| File | Change | Type |
|------|--------|------|
| `shapes/gif/src/GifDrawer.ts` | New — IShapeDrawer with GIF cache | New |
| `shapes/gif/src/GifUtils/Utils.ts` | Copied + adapted (drawGif, no loadGifImage) | Copy+edit |
| `shapes/image/src/Utils.ts` | Remove 7 gif fields + import | Modify |
| `shapes/image/src/ImageDrawer.ts` | Remove drawGif branch, gif fields | Modify |
| `bundles/all/src/index.ts` | Add loadGifShape import + call | Modify |

### User-facing config change

```jsonc
// Before (4.2.x):
{ "shape": { "type": "image", "options": { "image": { "gif": true, "src": "...", "width": 200, "height": 200 } } } }

// After (4.3.0):
{ "shape": { "type": "gif", "options": { "gif": { "src": "...", "width": 200, "height": 200 } } } }
```

---

## Release Infrastructure

### Root `package.json` version sync

Il root `package.json` ha un campo `"version"` che spesso rimane indietro rispetto ai package del workspace quando si lancia `lerna version` o un comando simile, perché questi tool aggiornano solo i package figli.

**Task:** Creare uno script (es. `scripts/sync-root-version.js`) che legga la versione da un package del workspace (es. `engine/package.json`) e la scriva nel root `package.json`. Poi agganciarlo al comando di version bump in modo che finisca **nello stesso commit/tag**, ad esempio:

- via `"version"` lifecycle hook nel root `package.json`: `"version": "node scripts/sync-root-version.js && git add package.json"`
- o integrato direttamente nello script che lancia il version bump

Lo script deve:
1. Leggere `engine/package.json` → `version`
2. Leggere `package.json` root → se diverso, sovrascrivere
3. Salvare e loggare il cambio

Il `git add package.json` nel lifecycle hook garantisce che la modifica sia inclusa nel commit di version bump, non in un commit separato.

---

## Rollback Plan

If any feature is not ready at the 4.3.0 freeze date:

| Scenario | Action |
|----------|--------|
| A not ready | Ship B + C as 4.3.0, defer A to 4.4.0 |
| B not ready | Ship A + C as 4.3.0, defer B to 4.4.0 |
| C not ready | Ship A + B as 4.3.0, defer C to 4.4.0 |
| A+B not ready | Ship C alone as 4.3.0 |
| A+B+C not ready | No 4.3.0 — reassess scope |

Each feature's branch must be independent so partial shipping is trivial.


Loading
Loading