# Project export: LEGATO

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: Compose. Connect. See. Hear. Understand. Enjoy. Repeat!
- Devpost: https://devpost.com/software/legato-krbepy
- GitHub: https://github.com/Feegoat06/LEGATO/
- Video: https://www.youtube.com/embed/dn5ygR-Hk00?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Chengxian (Eric) Jin (57 commits), FeiyiChen (29 commits), Junlin831 (12 commits)

## Devpost submission (written by the team)

### Inspiration

This story starts with Louie, one of LEGATO's creators. Freshman year, I liked a girl who looked like an angel - but more importantly, sang like one. One night during orientation week, we ditched whatever event we were supposed to attend and ended up in a church with a grand piano sitting open in the corner. She wanted to sing. She wanted me on piano behind her. I'd been playing since I was five, but only from sheet music. Put Mozart, Bach, and Beethoven in front of me all at once and I wouldn't break a sweat. But I couldn't sit at that piano, listen to her land on a note, and find the right chord to play next. That gap stayed with me longer than the night did. I understood harmony as something I memorized for an exam, not something I could use freely. Many classically trained pianists end up in the same place: drilled on notation for a decade, but never taught how to improvise. I wanted to build the tool I wish I'd had that night - something that would let me create a transition myself, hear it immediately, and understand why it worked. Not another flashcard quiz, and not a progression generator asking me to trust its answer. That idea became LEGATO.

### What it does

LEGATO is an AI-powered progression coach that helps musicians explore the space between chords. Users can build a chord progression, choose how long each chord lasts, and customize the transition between any two chords. LEGATO supports techniques such as passing diminished chords, secondary dominants, tritone substitutions, ii-V-I movements, suspended passing chords, scale runs, and arpeggio bridges. Every change is immediately compiled into playable piano audio and rendered as animated sheet music. Users can hear the result, see exactly what is happening, and continue experimenting without leaving the composition workspace. Our AI companion, Tenutino, moves through the score alongside the playback. Users can ask it to: Explain a chord or transition Suggest ways to improve the progression Answer open-ended music theory questions Instead of replacing the musician, LEGATO keeps the user in control. The goal is not to generate a finished progression with one click - it is to help people develop the intuition to create one themselves.

### How we built it

LEGATO uses a lightweight, framework-free JavaScript architecture. The chord and transition engine compiles every musical decision into one shared event structure. That same structure drives the audio, sheet music, animation, and AI context, keeping every part of the experience musically consistent. We used: VexFlow to engrave responsive SVG sheet music Tone.js and the Web Audio API for piano sampling and transport scheduling Three.js, WebGL, and GLSL for the animated particle score OpenAI API for grounded explanations, suggestions, and multi-turn tutoring SortableJS for drag-and-drop progression editing Node.js for the server and secure API-key handling LocalStorage for browser-based projects and conversation history Tenutino’s position is calculated from the actual rendered measures. During playback, it follows musical time, moves between systems, jumps over notes extending above the staff, and returns to the most recently edited measure when playback finishes.

### Challenges we ran into

The hardest challenge was synchronizing three different systems: audio time, engraved notation, and real-time animation. Audio is scheduled by Tone.js, sheet music is positioned by VexFlow, and particles are rendered independently through WebGL. Early versions allowed each system to estimate progress differently. This caused notes, particles, highlights, and Tenutino to drift apart or jump at measure boundaries. We solved this by making the audio transport the authoritative clock. VexFlow now reports the exact rendered position of each note, which we associate with its musical beat. Particle reconstruction and Tenutino’s movement follow those shared musical anchors instead of approximating time from screen position. Another challenge was preserving visual quality without introducing lag. The notation uses tens of thousands of particles, so rebuilding geometry during playback caused occasional frame drops. We added particle preloading, sampling caches, deterministic reveal timing, and geometry locking during playback. Music theory accuracy created its own challenges. Enharmonic spelling, tied notes, incomplete measures, generated transition material, and technique eligibility all needed to remain correct across different keys and time signatures. This is also the first Hackathon for all three of us, and time management became a real problem. However, we managed to have everything done in time through effective teamwork. Cheers!

### Accomplishments we're proud of

We are especially proud that LEGATO feels like an instrument for learning rather than a form that produces an AI answer. Users can make a musical decision, hear it instantly, see its notation, ask why it works, and then revise it. That tight feedback loop is the heart of the project. We are also proud of: Building a custom progression and transition engine Keeping audio, notation, particles, and AI explanations grounded in the same musical data Creating a responsive particle score without sacrificing notation clarity Turning Tenutino into a companion that reacts to editing and playback Supporting project persistence, import, export, and multi-turn tutoring Building 97 automated tests for music theory, rhythm, playback, animation, persistence, and UI behavior

### What we learned

We learned that synchronization is more of a data-model problem than a visual problem. Once audio, notation, and animation shared the same musical timeline, many seemingly unrelated interface bugs disappeared. We also learned that AI is most useful in creative education when it is grounded in the user's actual work. A generic explanation of a secondary dominant is less valuable than an explanation referencing the exact notes, voicing, key, and transition the learner just created. Most importantly, we learned that educational software does not need to feel like homework. Feedback can be technically rigorous while still being warm, playful, and encouraging.

### What's next

Next, we want to expand LEGATO from a progression coach into a broader improvisation companion. Planned features include: MIDI keyboard input and live chord recognition More transition styles, voicing controls, and genre-specific suggestions Ear-training exercises generated from the user's own progressions Deeper AI suggestion previews with selective apply and undo Cloud synchronization and collaborative projects Additional instruments and playback sounds Mobile and accessibility improvements Personalized learning paths based on the concepts each user explores Our long-term goal is simple: help more people move beyond reproducing music from a page and experience the joy of creating it themselves.

## README (from the GitHub repository)

# LEGATO

<p align="center">
  <img src="assets/brand/legato-icon.png" alt="LEGATO logo" width="96">
</p>

<p align="center"><strong>An interactive AI music tutor for composing, hearing, and understanding chord transitions.</strong></p>

<p align="center">
  OpenAI Build Week · Education · Built with Codex and GPT-5.6
</p>

LEGATO helps intermediate-to-advanced pianists learn harmony through experimentation. Instead of generating a finished progression and hiding the reasoning, LEGATO lets a learner build exact chord voicings, connect them with a harmonic technique, see and hear the compiled result, and get help from our cute AI tutor, Tenutino. (Wordplay on *tenuto*, get it? :wink:)

The result is a complete learning loop: <u>**Compose. Connect. See. Hear. Understand. Enjoy. Repeat!**</u>

## Contents

- [App Tour](#app-tour)
- [The Problem We Solve](#the-problem-we-solve)
- [How we collaborated with Codex](#how-we-collaborated-with-codex)
- [Features and Functionality](#features-and-functionality)
- [For Judges: Get the App Going!](#for-judges-get-the-app-going)
- [Run Locally](#run-locally)
- [Project Storage and Portability](#project-storage-and-portability)
- [Architecture](#architecture)
- [Testing](#testing)

## App Tour

### Project Library

![LEGATO project library with local projects, folders, bundled demos, import and export actions, and a trash bin](docs/images/legato-project-library.png)

Create and organize local projects, move work between browsers with JSON import/export, or open a bundled progression for an immediate walkthrough.

### Progression Studio

![LEGATO progression studio showing editable chords and transitions beside synchronized sheet music and playback controls](docs/images/legato-progression-studio.png)

Edit exact chord voicings and transition techniques on the left while the compiled sheet music, playback controls, and Tenutino all remain synchronized on the right.

### Tenutino AI Tutor

![LEGATO progression studio with the Tenutino GPT-5.6 tutoring drawer open beside the score](docs/images/legato-tenutino-tutor.png)

Ask about the most recently edited chord or seam using score-aware **Explain**, **Suggestions**, and conversational **Ask** modes.

## The Problem We Solve

Music-theory resources often explain harmony away from the instrument, getting too bogged down in the pure theories and textbooks, while composition tools often produce or play music without teaching the decisions inside it. Musicians need a way to compare their own voicings and transitions in context - visually, gaurally, and conversationally - without surrendering control to an opaque generator.

LEGATO keeps the learner's notes as the source of truth. AI explains the learner's musical choices, it does not replace them.

## How we collaborated with Codex

We used Codex as an engineering collaborator across the full project lifecycle, not as a one-shot code generator. We began by giving Codex a written product plan, a canonical music data model, explicit acceptance criteria, and ownership boundaries. For each iteration, Codex inspected the existing repository, proposed or implemented a scoped change, ran the relevant tests, and helped us review regressions before the next decision.

### Decisions we've made

The *human* team (us!) retained full control on the product, music, engineering, and design direction. Our key decisions included:

- **Teach rather than generate.** We chose an Education product for musicians who already understand basic chords, with AI supporting active listening and revision instead of composing on the learner's behalf.
- **Store notes, not recipes.** An explicit MIDI-note array is authoritative. Root and quality are optional display hints, so learners keep complete control of voicing, octave, doubling, and inversion.
- **Make "what you see" equal "what you hear."** We chose one pure compiler and one segment contract for notation, playback, animation, and tutor grounding.
- **Constrain the theory engine.** We selected the eight techniques, their musical definitions, beat costs, eligibility rules, and the rule that generated material - but never a user's chord - may be voice-led.
- **Ground the tutor deterministically.** We decided code should calculate objective musical evidence and GPT-5.6 should explain it. The key signature is treated as a spelling instruction, not proof of a tonal centre.
- **Prefer a local-first, judge-runnable product.** We chose a build-free frontend, browser storage, versioned JSON import/export, bundled demos, and an optional server-only AI key.
- **Build a coherent studio experience.** We directed the editorial sand-and-hologram visual language, project library, interactive score, animated Tenutino character, and compact three-mode tutor experience.

### Where Codex accelerated the workflow

- Turned the agreed state and compiler contracts into a modular vertical slice spanning the theory engine, notation, playback, UI, persistence, and server endpoint.
- Helped refactor a fast-moving multi-branch codebase without abandoning the established data model or re-voicing user input.
- Implemented and debugged difficult edge cases such as seam preservation after reorder, technique beat budgets, cross-measure ties, enharmonic spelling, rests, compound-meter durations, pause/resume synchronization, wrapped score systems, and malformed tutor responses.
- Converted discovered bugs and musical invariants into regression tests, shortening the loop from observation to verified fix.
- Accelerated late-stage product polish: project folders and trash, bulk export, responsive score layout, transport states, accessible labels, startup choreography, particle timing, and Tenutino's score-following motion.
- Audited the final repository and submission requirements, then helped turn the implementation history into reproducible setup and testing documentation.

Codex provided speed and breadth; we supplied the problem, musical judgment, priorities, constraints, and final approval. When generated code or a proposed interaction conflicted with the learning goal or musical contract, we corrected the requirement and asked Codex to revise against the written source of truth.

### How GPT-5.6 contributed to the final result

GPT-5.6 is both part of the building workflow through Codex and a user-facing component of LEGATO. At runtime, the server calls GPT-5.6 through the OpenAI Responses API to transform verified score evidence into a warm, concise explanation, suggestion, or answer. The API key remains server-side, response formats are selected by tutor mode, and every response is validated before the UI renders it.


## Features and Functionality

### Compose with exact voicings

- Build chords on an interactive piano keyboard interface using 11 quality presets: Major, Minor, dominant 7, major 7, minor 7, diminished, diminished 7, half-diminished 7, sus2, sus4, and augmented.
- Toggle individual MIDI notes to control register, spacing, doubling, and inversion. The notes entered are the notes rendered and played.
- Add rests, set chord duration in counted beats, drag to reorder material, and edit or delete any item.
- Configure tempo, simple or compound meter, key signature, clef, accent colour, and chord-symbol typography.

### Explore harmonic transitions

Each seam between two chords can use a direct transition or one of eight techniques:

| Technique | What LEGATO inserts |
| --- | --- |
| Diatonic passing diminished | A diminished seventh chord approaching the target |
| Secondary dominant | A dominant seventh that tonicizes the arriving chord |
| Tritone substitution | A substitute dominant resolving into the target |
| 2-5-1 insert | A two-chord ii-V approach to the destination |
| Sus chord passing | A suspended sonority on the target root |
| Leading-tone bass | A single chromatic approach note below the target |
| Scale run | A capped chromatic line from the current top note toward the target |
| Arpeggiated bridge 

[README truncated for size]

## Detected evidence (automated analysis)

Indexed codebase: 74 recognized source files, 605 KB.
- CSS (language) — detected in the code
- HTML (language) — detected in the code
- JavaScript (language) — detected in the code
- Node.js (technology) — claimed on Devpost, not found in the code
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (86 of 86)

```
.DS_Store
.gitignore
api/coach.js
css/base.css
css/editor-pane.css
css/landing.css
css/layout.css
css/piano-modal.css
css/project-settings-modal.css
css/sheet-music-pane.css
css/startup-splash.css
css/styles.css
css/tenutino.css
css/transport.css
css/tutor-chat.css
docs/AGENTS.md
docs/data-model.md
docs/drag-reorder.md
docs/plan.md
docs/TenutinoV2.md
fonts/Edwin-BdIta.otf
fonts/Edwin-Bold.otf
fonts/Edwin-Italic.otf
fonts/Edwin-LICENSE.txt
fonts/Edwin-Roman.otf
fonts/MuseJazz-OFL.txt
fonts/MuseJazzText.otf
fonts/README.md
index.html
js/audio/playback.js
js/coach/coach.js
js/coach/evidence.js
js/coach/prompts.js
js/data/demo-projects.js
js/data/support-messages.js
js/engine/chords.js
js/engine/compile.js
js/engine/key-signature.js
js/engine/rhythm.js
js/engine/technique-eligibility.js
js/engine/techniques.js
js/engine/voicing.js
js/main.js
js/persistence.js
js/router.js
js/sheet-music/parkour.js
js/sheet-music/particles.js
js/sheet-music/render.js
js/state.js
js/theme.js
js/ui/dialog.js
js/ui/editor-panel.js
js/ui/icons.js
js/ui/landing-panel.js
js/ui/piano-modal.js
js/ui/project-settings-modal.js
js/ui/sheet-music-panel.js
js/ui/startup-splash.js
js/ui/tenutino.js
js/ui/transport.js
js/ui/tutor-chat.js
js/util/html.js
js/util/labels.js
js/util/midi.js
js/views/editor-view.js
js/views/landing-view.js
LICENSE
package.json
README.md
server.mjs
tests/audio.test.js
tests/chord-spelling.test.js
tests/coach.test.js
tests/compile.test.js
tests/folders.test.js
tests/key-signature.test.js
tests/parkour.test.js
tests/particles.test.js
tests/rests.test.js
tests/rhythm.test.js
tests/sheet-timeline.test.js
tests/startup-splash.test.js
tests/state.test.js
tests/tenutino.test.js
tests/tutor-chat.test.js
tests/ui-icons.test.js
```

### Dependencies

No dependency index available.

### Recent commits (newest first)

- improve README.md
- README.md updated
- Add MIT License to the project
- delete license
- MIT License updated
- refactor: update document titles and remove unused workspace element
- simplify color scheme control
- final
- add animation for prompt reply
- Merge branch 'main' of https://github.com/Feegoat06/OpenAI_Build_Week_Project
- prompting polished
- disable controls when playing
- major improvements to sheet music
- agent formatting changes
- Active learning removed
- clicking tenutino will now open panel as well
- update demo info
- tenutino menu fix
- various improvements
- Add export selected functionality and improve button states

## Key source files (fetched from GitHub, selected and truncated for size)

### docs/drag-reorder.md

```markdown
# Drag-to-reorder chord cards

Users can reorder the chords in a progression by dragging the grip handle at
the left edge of each chord card. Neighbouring cards animate out of the way
to preview the drop position; on release the progression re-compiles, the
sheet music re-renders, and the transition seams are reconciled.

## Interaction

- **Where to grab:** a six-dot grip icon in the slim gutter just inside each
  card's accent stripe (`.chord-drag-handle` in
  [css/editor-pane.css](../css/editor-pane.css)).
- **What you see mid-drag:** the moving card lifts slightly with a shadow;
  the position it left behind is drawn as a dashed accent outline
  (`.chord-row--drag-ghost`). Surrounding cards slide smoothly to make room
  — the FLIP animation is SortableJS's `animation: 200` option, no custom
  keyframes.
- **Transitions during a drag:** transition seams are interleaved between
  chord rows in the DOM but are not themselves draggable. They fade out for
  the duration of the drag (`.progression-list--dragging .transition-seam`)
  and are rebuilt in place by the post-reorder re-render.

## Seam behaviour after a reorder

A seam survives a reorder only if the exact ordered adjacency it belonged to
(`chordA.id -> chordB.id`) still exists in the new order. Adjacencies that
change are reset to a direct (no-technique) transition. This is handled by
`reconcileSeams()` in [js/state.js](../js/state.js), the same function used
for delete and insert — the reorder path just plugs into `replaceChords()`
in [js/views/editor-view.js](../js/views/editor-view.js) so nothing new was
needed on the state side.

If the currently selected seam belongs to an adjacency the reorder invalidated,
the expanded seam editor collapses automatically on the next render — the
editor panel prunes any expanded seam whose index falls out of range or
whose adjacency is no longer set to a technique.

## Files touched

| File | Role |
| ---- | ---- |
| [index.html](../index.html) | Loads SortableJS from jsdelivr. |
| [js/ui/icons.js](../js/ui/icons.js) | Adds the `grip` icon (six dots). |
| [js/ui/editor-panel.js](../js/ui/editor-panel.js) | Renders the handle on each row, initialises SortableJS on the progression list, exposes `unmount()`. |
| [js/views/editor-view.js](../js/views/editor-view.js) | `onReorderChords` callback routes the new chord order through `replaceChords()`; the panel's `unmount()` is called during view teardown. |
| [css/editor-pane.css](../css/editor-pane.css) | Adds the handle column to `.chord-row`, styles the handle, drag-state classes, and seam fade-out. |

## Known limitations

- **Keyboard-only reordering is not wired.** SortableJS is pointer-driven; a
  future pass could add up/down key handling on `.chord-drag-handle` for
  accessibility. The handle carries `tabindex="-1"` today to keep it out of
  the tab order rather than expose an unusable focus stop.
- **Reordering mid-playback stops playback.** This follows the existing
  `rerender()` contract — eve
[truncated — 66 more characters]
```

### docs/AGENTS.md

```markdown
# LEGATO — Codex Build Plan for the First Working Sample

## 1. Mission

Build the first runnable vertical slice of **LEGATO**, an AI-assisted educational composition tool for intermediate-to-advanced pianists. Beginners may use it, but the product assumes users already understand chords, chord quality, and reading notation in common clefs.

LEGATO should help pianists, in this priority order:

1. Compose better chord progressions.
2. Understand chord transitions.
3. Improve voice leading.
4. Learn applicable music theory through listening and experimentation.

The product is a teaching tool first and a creativity tool second. It should not merely generate progressions: it should help learners understand their own musical decisions.

## 2. Instructions to Codex

Work autonomously until the first sample is runnable and tested. Begin by inspecting the repository and existing files before editing anything. Preserve working code and adapt this plan to the actual repository rather than recreating files unnecessarily.

Before implementation:

1. Read `docs/data-model.md` in full.
2. Read the existing `js/state.js`, if present.
3. Read the existing coach prompt and project plan.
4. Report any conflict between the data-model document, `state.js`, and this plan before changing the conflicting behavior.

Authority order:

1. `js/state.js` is authoritative for runtime shapes and factories.
2. `docs/data-model.md` is authoritative for field meaning and engine behavior.
3. This plan defines product scope, milestones, and acceptance criteria.

Do not silently invent fields, alter the eight defined techniques, re-voice user chords, or change timing semantics. If a required choice is not covered by these sources, choose the smallest reversible implementation, document the assumption, and keep it outside the stored data contract when possible.

## 3. Product Positioning

Use this sentence as the product north star:

> LEGATO is an interactive AI music tutor that helps pianists compose stronger chord progressions by letting them hear, see, and understand chord transitions and voice leading.

Primary Build Week category: **Education**.

Primary learning loop:

1. The pianist creates or selects two or more explicitly voiced chords.
2. LEGATO displays exactly those pitches in notation and plays exactly those pitches.
3. The pianist selects a transition technique for a seam.
4. LEGATO generates the connecting material, displays it, and plays it.
5. The pianist asks the AI coach what happened, why it works, and what to try next.
6. The pianist changes the voicing or transition and compares the result by ear.

## 4. Non-Negotiable Data Contract

Follow `docs/data-model.md`. The following is only a checklist, not a replacement for that document.

### Progression state

```js
progression = {
  settings: {
    tempo: 100,
    timeSig: { num: 4, den: 4 },
    key: 0,
    clef: 'auto'
  },
  chords: [
    {
      id,
      notes: [60, 64, 67],
      bars: 1,
      hint: { ro
[truncated — 17320 more characters]
```

### package.json

```
{
    "name": "legato-education",
    "version": "0.1.0",
    "private": true,
    "type": "module",
    "scripts": {
        "start": "node server.mjs",
        "test": "node --test"
    },
    "engines": {
        "node": ">=20"
    }
}
```

### server.mjs

```
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { extname, join, normalize } from 'node:path';
import { generateCoachResponse } from './api/coach.js';

const root = process.cwd();
const port = Number(process.env.PORT || 8000);
const types = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.mjs': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.otf': 'font/otf', '.woff2': 'font/woff2' };

createServer(async (request, response) => {
  try {
    if (request.url === '/api/coach.js') {
      if (request.method !== 'POST') { response.writeHead(405, { 'Content-Type': 'application/json' }); return response.end(JSON.stringify({ error: 'Method not allowed.' })); }
      let body = ''; for await (const chunk of request) body += chunk;
      const result = await generateCoachResponse(JSON.parse(body || '{}'));
      response.writeHead(200, { 'Content-Type': 'application/json' }); return response.end(JSON.stringify(result));
    }
    const pathname = request.url === '/' ? '/index.html' : decodeURIComponent(request.url.split('?')[0]);
    const path = normalize(join(root, pathname));
    if (!path.startsWith(root)) throw Object.assign(new Error('Forbidden'), { status: 403 });
    const data = await readFile(path);
    response.writeHead(200, { 'Content-Type': types[extname(path)] || 'application/octet-stream' }); response.end(data);
  } catch (error) {
    const status = error.code === 'ENOENT' ? 404 : error.status || 500;
    response.writeHead(status, { 'Content-Type': request.url.startsWith('/api/') ? 'application/json' : 'text/plain' });
    response.end(request.url.startsWith('/api/') ? JSON.stringify({ error: error.message }) : `${ status } ${ error.message }`);
  }
}).listen(port, () => console.log(`LEGATO running at http://localhost:${ port }`));

```

### js/main.js

```javascript
/**
 * Application bootstrap.
 *
 * Two views (landing, editor) and one router. Everything else — progression
 * state, sheet music rendering, audio, coach — lives inside the editor view
 * so navigating away and back gives a clean slate.
 *
 *   #/           → landing view
 *   #/edit/:id   → editor view
 *
 * The piano modal is mounted once here (shared across sessions) because it
 * is stateful DOM the editor opens and closes many times per session; a per-
 * mount rebuild would slow chord edits down for no gain.
 */
import { mountPianoModal } from './ui/piano-modal.js';
import { mountProjectSettingsModal } from './ui/project-settings-modal.js';
import { createProjectStore } from './persistence.js';
import { createRouter, makeEditorResumePolicy, parseEditorHash, LANDING_HASH } from './router.js';
import { createLandingView } from './views/landing-view.js';
import { createEditorView } from './views/editor-view.js';
import {
  beginStartupHandoff,
  completeStartupHandoff,
  mountStartupSplash,
  shouldShowStartupSplash,
  waitForStartupHandoffTarget,
} from './ui/startup-splash.js';

const appRoot = document.querySelector('#app-root');
const startupSplash = appRoot.querySelector('.startup-splash');
const pianoDialog = mountPianoModal({
  container: document.querySelector('#piano-modal-mount'),
});
const projectSettingsDialog = mountProjectSettingsModal({
  container: document.querySelector('#project-settings-modal-mount'),
});

const store = createProjectStore();
const landingView = createLandingView({ store, projectSettingsDialog });
const editorView = createEditorView({
  store,
  pianoDialog,
  projectSettingsDialog,
});

const router = createRouter({
  root: appRoot,
  routes: [
    { match: (hash) => (hash === LANDING_HASH ? {} : null), view: landingView },
    { match: (hash) => parseEditorHash(hash), view: editorView },
  ],
  notFound: landingView,
  resume: makeEditorResumePolicy(store),
});

if (startupSplash && shouldShowStartupSplash()) {
  // Keep the splash outside the router root so the destination view can mount
  // invisibly behind its black handoff stage.
  document.body.append(startupSplash);
  const splashController = mountStartupSplash(startupSplash);
  const startupTenutino = await beginStartupHandoff(startupSplash);
  splashController.destroy();
  await router.start();
  const destination = await waitForStartupHandoffTarget(appRoot);
  await completeStartupHandoff(startupSplash, startupTenutino, destination);
} else {
  startupSplash?.remove();
  await router.start();
}

```

### index.html

```html
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width,initial-scale=1">
  <meta name="description"
    content="LEGATO is an interactive AI music tutor for composing, hearing, and understanding chord transitions.">
  <title>Home - LEGATO</title>
  <link rel="icon" type="image/png" href="/assets/brand/legato-icon.png">
  <link rel="stylesheet" href="/css/styles.css">
  <script>
    // Mark reloads before the first paint so the startup overture never
    // flashes when the user refreshes the current page.
    if (performance.getEntriesByType('navigation')[0]?.type === 'reload') {
      document.documentElement.dataset.legatoNavigation = 'reload';
    }
  </script>
</head>

<body>
  <!--
    Router-driven shell. The router mounts landing-view or editor-view into
    #app-root based on the hash route. The piano modal is mounted once and
    reused by the editor across sessions.
  -->
  <div id="app-root">
    <section class="startup-splash" role="status" aria-live="polite" aria-label="LEGATO is loading">
      <header class="startup-splash-header">
        <div class="startup-splash-brand">
          <img src="/assets/brand/legato-icon.png" alt="" draggable="false">
          <span>LEGATO</span>
        </div>
      </header>

      <div class="startup-splash-content">
        <img class="startup-splash-tenutino" src="/assets/tenutino/tenutino.png" alt="" draggable="false">
        <p id="startup-splash-message" class="startup-splash-message">Take your time. We&rsquo;ll find the way forward.</p>
        <p class="startup-splash-status">Preparing your composition workspace&hellip;</p>

        <div class="startup-splash-score-stage" data-particle-stage aria-hidden="true">
          <svg class="startup-splash-score" viewBox="0 0 920 76" style="color: #d1a15a">
            <g class="startup-splash-staff" fill="none" stroke="#d1a15a" stroke-width="1.5" opacity="0.55">
              <path d="M18 20H902M18 29H902M18 38H902M18 47H902M18 56H902" />
              <path d="M18 15V61M902 15V61" />
            </g>
            <g class="startup-splash-chord startup-splash-chord-start" fill="#d1a15a" stroke="#d1a15a" stroke-width="1.2">
              <ellipse cx="47" cy="29" rx="6" ry="4" />
              <ellipse cx="47" cy="38" rx="6" ry="4" />
              <ellipse cx="47" cy="47" rx="6" ry="4" />
              <path d="M53 29V12" />
            </g>
            <g class="startup-splash-passing-notes" fill="#d1a15a" stroke="#d1a15a" stroke-width="1.2" opacity="0.9">
              <ellipse cx="215" cy="45" rx="5" ry="3.5" /><path d="M220 45V29" />
              <ellipse cx="370" cy="38" rx="5" ry="3.5" /><path d="M375 38V22" />
              <ellipse cx="525" cy="31" rx="5" ry="3.5" /><path d="M530 31V15" />
              <ellipse cx="680" cy="38" rx="5" ry="3.5" /><path d="M685 38V22" />
            </g>
            <g class="startup-splash-chord startup-splash-chord-end" fill="#d1a15a" stroke="#d1a15a" stroke-width="1.2" opacity="0.82">
              <ellipse cx="873" cy="29" rx="6" ry="4" />
              <ellipse cx="873" cy="38" rx="6" ry="4" />
              <ellipse cx="873" cy="47" rx="6" ry="4" />
              <path d="M879 29V12" />
            </g>
          </svg>
          <canvas class="startup-splash-particles" aria-hidden="true"></canvas>
        </div>
      </div>
    </section>
  </div>
  <div id="piano-modal-mount"></div>
  <div id="project-settings-modal-mount"></div>

  <script src="https://cdn.jsdelivr.net/npm/vexflow@4.2.5/build/cjs/vexflow.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/tone@14.8.49/build/Tone.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.6/Sortable.min.js"></script>
  <script type="module" src="/js/main.js"></script>
</body>

</html>

```

### tests/tutor-chat.test.js

```javascript
import test from 'node:test';
import assert from 'node:assert/strict';
import { modeForComposerSubmission } from '../js/ui/tutor-chat.js';

test('a manually submitted Tutor message always uses conversational Ask mode', () => {
  assert.deepEqual(
    ['explain', 'suggest', 'ask'].map((mode) => modeForComposerSubmission(mode)),
    ['ask', 'ask', 'ask'],
  );
});

```

### tests/ui-icons.test.js

```javascript
import test from 'node:test';
import assert from 'node:assert/strict';
import { icon } from '../js/ui/icons.js';

test('every registered UI icon renders SVG markup', () => {
  const names = ['plus', 'minus', 'close', 'edit', 'rename', 'duplicate', 'export', 'trash', 'play', 'pause', 'stop', 'home', 'github', 'chevronDown', 'chevronUp', 'chevronLeft', 'arrowRight', 'folder', 'check', 'grip', 'density'];
  for (const name of names) {
    assert.match(icon(name), new RegExp(`data-icon="${ name }"`));
  }
});

```

### css/styles.css

```css
/* Stylesheet barrel — order matters: base tokens/primitives, then layout,
   then each panel. Each per-panel file owns its own classes and media
   queries so parallel edits don't overlap on the same file. */

@import url('./base.css');
@import url('./startup-splash.css');
@import url('./layout.css');
@import url('./landing.css');
@import url('./editor-pane.css');
@import url('./sheet-music-pane.css');
@import url('./tenutino.css');
@import url('./transport.css');
@import url('./tutor-chat.css');
@import url('./piano-modal.css');
@import url('./project-settings-modal.css');

```

### tests/sheet-timeline.test.js

```javascript
import test from 'node:test';
import assert from 'node:assert/strict';

import { timelineAnchorsForNotes } from '../js/sheet-music/render.js';

test('engraved note positions retain their audio beat onsets', () => {
  const segments = [{ startBeat: 0 }, { startBeat: 1 }, { startBeat: 3 }];
  const notes = [95, 180, 290].map((x) => ({ getAbsoluteX: () => x }));

  assert.deepEqual(timelineAnchorsForNotes(segments, notes, 4), [
    { x: 95, progress: 0 },
    { x: 180, progress: 0.25 },
    { x: 290, progress: 0.75 },
  ]);
});

test('timeline anchors sort by final VexFlow position', () => {
  const segments = [{ startBeat: 2 }, { startBeat: 0 }];
  const notes = [{ getAbsoluteX: () => 240 }, { getAbsoluteX: () => 100 }];

  assert.deepEqual(timelineAnchorsForNotes(segments, notes, 4), [
    { x: 100, progress: 0 },
    { x: 240, progress: 0.5 },
  ]);
});

```

[64 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]