Skip to content

Commit 46d552b

Browse files
authored
Merge pull request #19 from moises-ai/sync-upstream-2026-03-19
chore: sync with upstream 2026-03-19
2 parents 78cdb41 + dd4ffb7 commit 46d552b

File tree

343 files changed

+8611
-1322
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

343 files changed

+8611
-1322
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
name: Never use 'as any'
3+
description: User strongly prohibits using 'as any' type casts - always use proper types instead
4+
type: feedback
5+
---
6+
7+
Never use `as any` in this codebase. Always define proper types. If a value needs a type that doesn't exist yet, add the field to the type definition (e.g., `id?: int` on UserOutput) rather than casting with `as any`.
8+
9+
**Why:** The user considers `as any` a serious code quality violation. It bypasses the type system entirely and defeats the purpose of using TypeScript.
10+
11+
**How to apply:** When you need to access a property that isn't on the current type, extend the type to include it. Never cast to `any` as a shortcut.

.claudeignore

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
.DS_Store
2+
.turbo
3+
.idea
4+
packages/app/studio/.env
5+
.cache
6+
*.log
7+
*.local
8+
node_modules
9+
dist
10+
dist-ssr
11+
server/dist
12+
public/dist
13+
public/build-info.json
14+
lerna-debug.log
15+
/packages/lib/box-forge/test/gen/**
16+
/packages/studio/boxes/src/**
17+
/packages/studio/adapters/src/index.ts
18+
/packages/app/studio/public/build-info.json
19+
/certs/localhost.pem
20+
/certs/localhost-key.pem
21+
/packages/app/soundfont/public/soundfonts/
22+
/packages/studio/scripting/src/api.declaration.d.ts
23+
tsconfig.tsbuildinfo

.github/workflows/deploy.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@ jobs:
1111

1212
steps:
1313
- name: ⬇️ Checkout main repo
14-
uses: actions/checkout@v4
14+
uses: actions/checkout@v5
1515
with:
1616
fetch-depth: 0
1717

1818
- name: 🦄 Set up Node.js
19-
uses: actions/setup-node@v4
19+
uses: actions/setup-node@v5
2020
with:
21-
node-version: 20
21+
node-version: 22
2222
cache: npm
2323

2424
- name: 📦 Install dependencies

.github/workflows/discord.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ jobs:
99
env:
1010
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
1111
steps:
12-
- uses: actions/checkout@v3
13-
- uses: actions/setup-node@v3
12+
- uses: actions/checkout@v5
13+
- uses: actions/setup-node@v5
1414
with:
15-
node-version: 20
15+
node-version: 22
1616
- run: npm ci
1717
- run: npx ts-node --transpile-only deploy/discord.ts

.github/workflows/test-sftp.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@ jobs:
1717

1818
steps:
1919
- name: ⬇️ Checkout repo
20-
uses: actions/checkout@v4
20+
uses: actions/checkout@v5
2121

2222
- name: 🦄 Set up Node.js
23-
uses: actions/setup-node@v4
23+
uses: actions/setup-node@v5
2424
with:
25-
node-version: 20
25+
node-version: 22
2626

2727
- name: 📦 Install SFTP client
2828
run: npm install ssh2-sftp-client

.moises-scope-applied

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
Scope transformation applied: 2026-03-03T18:31:39.617Z
1+
Scope transformation applied: 2026-03-19T21:06:47.757Z
22
Old scope: @opendaw
33
New scope: @moises-ai

README.md

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,27 @@ The source code for openDAW is available under **AGPL v3 (or later)**
4040

4141
---
4242

43+
## Looking for Contributors
44+
45+
We welcome contributions that follow the existing style and conventions of the project. AI-assisted code is fine, but
46+
every contributor must **understand every line of code they submit**. If you use AI tools, please document your process
47+
in [`/plans`](https://github.com/andremichelle/openDAW/tree/main/plans). Keep pull requests small and focused. Large
48+
PRs will not be reviewed. Split big contributions into smaller commits that add requirements gradually and maintain
49+
operations of the app.
50+
51+
If you are interested in helping, here are areas where we need support:
52+
53+
1. **Y.JS Server + WebRTC File Sharing** - for live collaboration
54+
2. **Offline App** — e.g. wrapping openDAW with [Tauri](https://tauri.app/) for a native desktop experience
55+
3. **PWA** — turning openDAW into a fully installable Progressive Web App with offline support
56+
4. **Timeline Track Management** — design and UX help for track layout, ordering, grouping, and interaction
57+
58+
We always appreciate help on open issues: https://github.com/andremichelle/openDAW/issues
59+
60+
To discuss contributions, book a call: https://calendly.com/andremichelle/opendaw-on-tour
61+
62+
---
63+
4364
## Huge Shoutout To The Incredible openDAW Community!
4465

4566
To everyone who has contributed feedback, reported bugs, suggested improvements, or helped spread the word — thank you!
@@ -50,7 +71,8 @@ you [@ccswdavidson](https://github.com/ccswdavidson), [@Chaosmeister](https://gi
5071
and [@xnstad](https://github.com/xnstad) for testing the repositories and identifying issues during the installation of
5172
openDAW!
5273

53-
Special shout-out to the biggest bug hunters: [kanaris](https://kanaris.net/), [@Chaosmeister](https://github.com/Chaosmeister)
74+
Special shout-out to the biggest bug
75+
hunters: [kanaris](https://kanaris.net/), [@Chaosmeister](https://github.com/Chaosmeister)
5476
and [BeatMax Prediction](https://linktr.ee/beatmax_prediction). Your relentless attention to detail made a huge
5577
difference!
5678

@@ -64,7 +86,9 @@ Stephen Tai, Pathfinder, One Sound Every Day (santino), kanaris, Oli Larkin
6486

6587
### openDAW Supporter — $5.00
6688

67-
Cal Lycus, Jetdarc, Truls Enstad, Polarity, Ynot Etluhcs, Mats Gisselson, Ola, SKYENCE, BeatMax_Prediction, Kim T, Nyenoidz, Steve Meiers, 4ohm, Yito, Shawn Lukas, Tommes, David Thompson, Harry Gillich, OxVolt, Wojciech Miłkowski, skyboundzoo, JHINZ, Mark Dammer, fork-kun, Martin Eigel
89+
Cal Lycus, Jetdarc, Truls Enstad, Polarity, Ynot Etluhcs, Mats Gisselson, Ola, SKYENCE, BeatMax_Prediction, Kim T,
90+
Nyenoidz, Steve Meiers, 4ohm, Yito, Shawn Lukas, Tommes, David Thompson, Harry Gillich, OxVolt, Wojciech Miłkowski,
91+
skyboundzoo, JHINZ, Mark Dammer, fork-kun, Martin Eigel
6892

6993
---
7094

docs/graph.md

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
# Box Graph Internals
2+
3+
## Transaction Model
4+
5+
`BoxEditing.modify()` wraps all box graph mutations in a transaction:
6+
7+
```
8+
beginTransaction()
9+
modifier() ← user code runs here (box creation, deletion, pointer changes)
10+
endTransaction() ← deferred pointer notifications fire here
11+
validateRequirements()
12+
mark()
13+
notifier.notify() ← BoxEditing subscribers notified (undo/redo state)
14+
```
15+
16+
### Nested `modify()` calls
17+
18+
When `modify()` is called while `#modifying` is true or the graph is in a transaction,
19+
it takes a shortcut path: it calls `this.#notifier.notify()` and then `modifier()` directly,
20+
without starting a new transaction. The box operations run inside the existing outer transaction.
21+
22+
### Pointer Update Deferral
23+
24+
During a transaction, pointer changes (e.g., `pointer.refer(target)`, `pointer.defer()`)
25+
are recorded in `#pointerTransactionState` but NOT applied immediately.
26+
27+
At `endTransaction()`, the deferred pointer changes are processed:
28+
29+
```typescript
30+
this.#pointerTransactionState.values()
31+
.toSorted((a, b) => a.index - b.index)
32+
.forEach(({pointer, initial, final}) => {
33+
if (!initial.equals(final)) {
34+
initial.ifSome(address => findVertex(address)?.pointerHub.onRemoved(pointer))
35+
final.ifSome(address => findVertex(address)?.pointerHub.onAdded(pointer))
36+
}
37+
})
38+
```
39+
40+
This means `pointerHub.onRemoved` / `onAdded` callbacks fire AFTER all mutations complete,
41+
during `endTransaction()`. Code subscribed via `pointerHub.catchupAndSubscribe()` (e.g.,
42+
`VertexSelection.#watch`) sees the changes only at this point.
43+
44+
After pointer processing, `#inTransaction` is set to false. Then `#finalizeTransactionObservers`
45+
are executed (these can add more observers in a loop). Finally `onEndTransaction` fires.
46+
47+
## Box Deletion and Cascade
48+
49+
`box.delete()` computes dependencies via `graph.dependenciesOf(box)`:
50+
51+
1. Follows **outgoing** pointers to downstream targets
52+
2. Follows **incoming** pointers that are `mandatory` to upstream boxes
53+
3. Collects all dependent boxes and pointers recursively
54+
55+
Then:
56+
- All collected pointers are deferred (`pointer.defer()`)
57+
- All collected dependent boxes are unstaged (`box.unstage()`)
58+
- The root box is unstaged
59+
60+
### Cascade Deletion via `Field.disconnect()`
61+
62+
When a box is unstaged, its fields call `disconnect()`. For target fields with incoming pointers:
63+
64+
```typescript
65+
disconnect(): void {
66+
const incoming = this.pointerHub.incoming()
67+
incoming.forEach(pointer => {
68+
pointer.defer()
69+
if (pointer.mandatory || (this.pointerRules.mandatory && incoming.length === 1)) {
70+
pointer.box.delete() // CASCADE: deletes the box that owns the mandatory pointer
71+
}
72+
})
73+
}
74+
```
75+
76+
**Key implication**: If Box A has a `mandatory` pointer to Box B, deleting Box B
77+
will cascade-delete Box A within the same transaction.
78+
79+
### SelectionBox Cascade
80+
81+
`SelectionBox` has two mandatory pointers:
82+
- `selection` → the user's selection field
83+
- `selectable` → the selected vertex (e.g., a region box)
84+
85+
When a region box is deleted, `disconnect()` on the region's field finds the SelectionBox's
86+
`selectable` pointer (which is mandatory) and cascade-deletes the SelectionBox.
87+
88+
At `endTransaction()`, the SelectionBox's `selection` pointer fires `onRemoved` on the
89+
user's selection field, which triggers `VertexSelection.#watch.onRemoved`. This removes
90+
the entry from `#entityMap` and `#selectableMap`, and notifies `onDeselected` listeners.
91+
92+
## VertexSelection and the `#watch` Mechanism
93+
94+
`VertexSelection.#watch(target)` subscribes to the user's selection field's `pointerHub`:
95+
96+
- **`onAdded`**: A new SelectionBox was created → adds entry to `#entityMap` and `#selectableMap`,
97+
notifies `onSelected` listeners
98+
- **`onRemoved`**: A SelectionBox was deleted → removes entry from both maps,
99+
notifies `onDeselected` listeners (which propagates to `FilteredSelection`)
100+
101+
These callbacks fire during `endTransaction()`, NOT during `modifier()` execution.
102+
103+
## Timing of Side Effects
104+
105+
Within `BoxEditing.modify()`:
106+
107+
| Phase | `#modifying` | `inTransaction()` | Pointer notifications | `#selectableMap` updates |
108+
|-------|-------------|-------------------|----------------------|------------------------|
109+
| Before `beginTransaction()` | true | false | No | No |
110+
| During `modifier()` | true | true | **Deferred** | No |
111+
| During `endTransaction()` | true | transitions to false | **Firing** | **Yes** |
112+
| After `endTransaction()` | true→false | false | Done | Done |
113+
| `notifier.notify()` | false | false | Done | Done |
114+
115+
This means code running inside `modifier()` can safely iterate `#selectableMap`
116+
because it won't change until `endTransaction()`. But code triggered BY `endTransaction()`
117+
(via `onRemoved`/`onAdded` cascades, `finalizeTransactionObservers`, or `onEndTransaction`)
118+
runs AFTER the map has been modified.
119+
120+
## Known Issue: Stale Deselection After Region Deletion
121+
122+
When a region is deleted by the ClipResolver (e.g., during content-start trimming with overlap
123+
resolution), the cascade deletes the SelectionBox and cleans up `#selectableMap` at
124+
`endTransaction()`. If a reactive observer later tries to `deselect` the same region
125+
(e.g., from an animation frame callback), `#selectableMap.get()` throws "Unknown key"
126+
because the entry was already removed.
127+
128+
Introduced by commit `608f0b48` ("prevent overlapping", Jan 26 2026) which added the
129+
overlap resolver to `RegionContentStartModifier.approve()`.

0 commit comments

Comments
 (0)