Skip to content
Merged

2.0.6 #276

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
aa4baa3
Fixes GUI
BartolomeoRusso9 Nov 12, 2026
99a9d1d
Fix GUI
BartolomeoRusso9 Nov 12, 2026
5849e10
Add a Player, local file tools, weekly releases, concerts and lyrics …
BartolomeoRusso9 Nov 12, 2026
400b391
Black .
BartolomeoRusso9 Nov 12, 2026
c537c0a
#277
BartolomeoRusso9 Nov 12, 2026
a986f9c
github security fixes
BartolomeoRusso9 Nov 12, 2026
961e9ae
Update webapp.py
BartolomeoRusso9 Nov 12, 2026
4dc9870
Update webapp.py
BartolomeoRusso9 Nov 12, 2026
8c6b323
Update webapp.py
BartolomeoRusso9 Nov 12, 2026
65e3683
Keep a reference to fire-and-forget asyncio tasks
BartolomeoRusso9 Nov 12, 2026
cc04841
Add the GUI/TUI redesign plan
BartolomeoRusso9 Nov 12, 2026
65ca900
Clear the mypy backlog and make the CI step blocking
BartolomeoRusso9 Nov 12, 2026
962a344
Refuse DTDs in XML fetched from WebDAV servers and lyrics services
BartolomeoRusso9 Nov 12, 2026
3e6f280
Move folder browsing, media serving and segment merging off the event…
BartolomeoRusso9 Nov 12, 2026
2c6759a
Test every declared Python version and macOS in CI; align the black hook
BartolomeoRusso9 Nov 12, 2026
c9d2d1d
Give extensions the mobile runtime's synchronous fetch()
BartolomeoRusso9 Nov 12, 2026
2842669
Search Amazon, YouTube Music and other search-only extensions; stop m…
BartolomeoRusso9 Nov 12, 2026
a2c6d4c
Fill the host gaps the Jellyfin extension ran into
BartolomeoRusso9 Nov 12, 2026
82328df
Record progress, search and Jellyfin findings in the redesign plan
BartolomeoRusso9 Nov 12, 2026
6c9011f
Test the id, handler and class contract between index.html and app.js
BartolomeoRusso9 Nov 12, 2026
6ad44f5
Capture every TUI panel to SVG as the pre-redesign baseline
BartolomeoRusso9 Nov 12, 2026
f5abb18
Let one extension reach the server in its own settings on the LAN
BartolomeoRusso9 Nov 12, 2026
00b7ac3
Use extensions that supply lyrics as lyrics providers
BartolomeoRusso9 Nov 12, 2026
af7a991
Record Fase 0, J1 and J2 progress in the redesign plan
BartolomeoRusso9 Nov 12, 2026
33c8b4a
Let Explore show the home feed of any extension that has one
BartolomeoRusso9 Nov 12, 2026
e26ecc5
Keep extension setting fields wide enough to type in
BartolomeoRusso9 Nov 12, 2026
641c0b1
Open links only an extension can read, and honour its skip flags
BartolomeoRusso9 Nov 12, 2026
f69b231
Wrap word-synced lyrics whose timed pieces hold several words
BartolomeoRusso9 Nov 12, 2026
0ceeb2b
Screenshot every GUI view as the pre-redesign baseline
BartolomeoRusso9 Nov 12, 2026
f963b5f
Record J3, J4, the lyrics fix and the finished Fase 0 in the redesign…
BartolomeoRusso9 Nov 12, 2026
a555fce
Record the confirmed visual defaults in the redesign plan
BartolomeoRusso9 Nov 12, 2026
105738d
Move index.html's inline styles into classes
BartolomeoRusso9 Nov 12, 2026
e5fcef9
Move the styles app.js writes into classes and tones
BartolomeoRusso9 Nov 12, 2026
8447302
Bring every colour in styles.css under a token
BartolomeoRusso9 Nov 12, 2026
4fd922c
Record the finished Fase 1 in the redesign plan
BartolomeoRusso9 Nov 12, 2026
87804a7
Note the full test run after Fase 1 in the redesign plan
BartolomeoRusso9 Nov 12, 2026
0e9fc45
Mock the console direction: Home, Queue and the TUI Download panel
BartolomeoRusso9 Nov 12, 2026
d3978bd
Update hires_check.py
BartolomeoRusso9 Nov 12, 2026
4aa33bc
Fix
BartolomeoRusso9 Nov 12, 2026
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
28 changes: 16 additions & 12 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ['3.11', '3.12']
# Every version the classifiers in pyproject.toml declare.
python-version: ['3.11', '3.12', '3.13', '3.14']

steps:
- name: Checkout code
Expand Down Expand Up @@ -47,12 +48,10 @@ jobs:
- name: Black (format check)
run: black --check .

- name: Mypy (advisory — see [tool.mypy] in pyproject.toml)
# Not blocking yet: the codebase predates type checking and carries a
# sizeable pre-existing error backlog. Surfaced in the log so it's
# visible and can be tightened incrementally, without turning every
# unrelated PR red today.
run: mypy mornye || true
- name: Mypy
# Blocking since the backlog reached zero (2026-11). The settings in
# [tool.mypy] are still lenient; tighten them module by module.
run: mypy mornye

- name: Pytest (with coverage)
run: pytest -v --cov=mornye --cov-report=term-missing --cov-fail-under=30
Expand Down Expand Up @@ -81,11 +80,16 @@ jobs:
node --check "$f"
done
test-windows:
# A pywebview desktop app lives or dies on Windows; the matrix above only
# ever ran on Linux. This doesn't repeat lint/format/mypy — just the test
# suite, to catch path-separator/encoding/subprocess issues Linux CI can't.
runs-on: windows-latest
test-desktop:
# A pywebview desktop app lives or dies on Windows and macOS; the matrix
# above only ever ran on Linux. This doesn't repeat lint/format/mypy —
# just the test suite, to catch path-separator/encoding/subprocess issues
# (and, on macOS, the pyobjc install) that Linux CI can't.
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [windows-latest, macos-latest]
steps:
- name: Checkout code
uses: actions/checkout@v5
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ htmlcov/
/.claude
/mornye.egg-info
/.mornye

# Fresh captures from tests/visual/capture_tui.py; the baseline is committed.
tests/visual/out/
4 changes: 3 additions & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ repos:
additional_dependencies: ["ruff>=0.12.0"]

- repo: https://github.com/psf/black
rev: 24.0
# Keep in step with the `black` floor in requirements-dev.txt: a hook on
# an older release formats differently from CI.
rev: 26.10.1
hooks:
- id: black
language_version: python3
153 changes: 153 additions & 0 deletions docs/mocks/console.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* docs/mocks/console.css — direction A, "Console da studio" (redesign-plan §2).
A static mock, not the app: values here are proposals for token v2 (voce 8). */

@font-face {
font-family: 'JetBrains Mono';
src: url('../../mornye/frontend/assets/fonts/JetBrainsMono-Variable.woff2') format('woff2');
font-weight: 100 800;
}

:root {
/* Graphite, no warm or blue cast: four surfaces, two rules. */
--g-0: #0d0d0d; /* ground */
--g-1: #141414; /* panel */
--g-2: #1b1b1b; /* hover, field */
--g-3: #242424; /* selected row */
--rule: #2c2c2c;
--rule-strong: #3d3d3d;
--text: #e8e8e8;
--text-2: #b5b5b5;
--muted: #858585;
/* The one signal: focus, selection, primary action, active level. */
--signal: #ffb000;
--signal-ink: #120d00;
/* State only, never decoration. */
--ok: #5ec46b;
--err: #ff5a4f;

--row: 32px;
--r: 2px;
--font: 'JetBrains Mono', ui-monospace, Menlo, monospace;
color-scheme: dark;
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; background: var(--g-0); color: var(--text); }
body {
font: 400 13px/1.4 var(--font);
font-variant-ligatures: none;
-webkit-font-smoothing: antialiased;
}

/* ── Shell ─────────────────────────────────────────────────────────────── */
.app { display: grid; grid-template: 32px 1fr 40px / 176px 1fr; height: 100vh; }

.titlebar {
grid-column: 1 / -1; display: flex; align-items: center; gap: 12px;
padding: 0 12px; border-bottom: 1px solid var(--rule); background: var(--g-1);
}
.wordmark { font-weight: 700; letter-spacing: .24em; font-size: 12px; }
.wordmark small { font-weight: 400; color: var(--muted); letter-spacing: .08em; margin-left: 8px; }
.titlebar .rule { flex: 1; height: 1px; background: var(--rule); }

.rail { border-right: 1px solid var(--rule); background: var(--g-1); padding: 8px 0; overflow: hidden; }
.rail-group { margin-bottom: 12px; }
.rail-head, .label {
font-size: 10.5px; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
}
.rail-head { padding: 8px 16px 4px; }
.rail a {
display: flex; align-items: center; gap: 10px; height: var(--row); padding: 0 16px;
color: var(--text-2); text-decoration: none; font-size: 12px; letter-spacing: .04em;
border-left: 2px solid transparent;
}
.rail a:hover { background: var(--g-2); color: var(--text); }
.rail a.on { border-left-color: var(--signal); color: var(--text); background: var(--g-2); }
.rail a .ico { width: 14px; text-align: center; color: var(--muted); }
.rail a.on .ico { color: var(--signal); }
.rail a .count { margin-left: auto; color: var(--signal); font-size: 11px; }

.main { overflow: auto; padding: 20px 20px 24px; display: flex; flex-direction: column; gap: 24px; }

/* ── Panels: the label sits in the border, like a silk-screened legend ── */
.panel { position: relative; border: 1px solid var(--rule); background: var(--g-1); border-radius: var(--r); }
.panel > .legend {
position: absolute; top: -8px; left: 12px; padding: 0 6px; background: var(--g-0);
font-size: 10.5px; font-weight: 500; letter-spacing: .14em; text-transform: uppercase; color: var(--muted);
line-height: 16px;
}
.panel > .legend b { color: var(--text); font-weight: 500; }
.panel > .legend-r { left: auto; right: 12px; }
.panel-body { padding: 16px; }

/* ── Controls ──────────────────────────────────────────────────────────── */
.field {
display: flex; align-items: center; height: var(--row); border: 1px solid var(--rule-strong);
background: var(--g-2); border-radius: var(--r); flex: 1; min-width: 0;
}
.field .pre { padding: 0 10px; color: var(--muted); border-right: 1px solid var(--rule); height: 100%; display: flex; align-items: center; font-size: 11px; letter-spacing: .12em; }
.field input { flex: 1; min-width: 0; background: none; border: 0; color: var(--text); font: inherit; padding: 0 10px; outline: none; }
.field:focus-within { border-color: var(--signal); }

.btn {
height: var(--row); padding: 0 14px; border: 1px solid var(--rule-strong); background: var(--g-2);
color: var(--text); font: 500 12px var(--font); letter-spacing: .06em; border-radius: var(--r);
cursor: pointer; white-space: nowrap; display: inline-flex; align-items: center; gap: 8px;
}
.btn:hover { border-color: var(--text-2); }
.btn.primary { background: var(--signal); border-color: var(--signal); color: var(--signal-ink); }
.btn.ghost { background: none; }
.btn:focus-visible, a:focus-visible { outline: 1px solid var(--signal); outline-offset: 2px; }
.key { font-size: 10.5px; color: var(--muted); border: 1px solid var(--rule-strong); padding: 0 4px; border-radius: var(--r); }

/* Signature 1 — the quality display: a readout, not a coloured badge. */
.qd {
display: inline-flex; align-items: center; height: 20px; padding: 0 6px; gap: 6px;
border: 1px solid var(--signal); color: var(--signal); border-radius: var(--r);
font-size: 11px; font-weight: 500; letter-spacing: .06em; white-space: nowrap;
}
.qd.dim { border-color: var(--rule-strong); color: var(--text-2); }
.state { font-size: 11px; letter-spacing: .1em; text-transform: uppercase; white-space: nowrap; }
.state.ok { color: var(--ok); }
.state.err { color: var(--err); }
.state.run { color: var(--signal); }
.state.idle { color: var(--muted); }

/* Signature 2 — the segment meter: LED steps, never a smooth fill. */
.meter { display: inline-flex; gap: 2px; height: 10px; align-items: stretch; vertical-align: middle; }
.meter i { width: 6px; background: var(--rule-strong); }
.meter i.on { background: var(--signal); }
.meter.lg { height: 14px; }
.meter.lg i { width: 8px; }
.meter.err i.on { background: var(--err); }
.meter.ok i.on { background: var(--ok); }

/* ── Tables: 32px rows, rules not cards ───────────────────────────────── */
.table { width: 100%; border-collapse: collapse; }
.table th {
height: 28px; text-align: left; font-size: 10.5px; font-weight: 500; letter-spacing: .14em;
text-transform: uppercase; color: var(--muted); border-bottom: 1px solid var(--rule); padding: 0 10px;
}
.table td { height: var(--row); padding: 0 10px; border-bottom: 1px solid var(--rule); white-space: nowrap; }
.table tr:last-child td { border-bottom: 0; }
.table tbody tr:hover td { background: var(--g-2); }
.table tr.sel td { background: var(--g-3); }
.table tr.sel td:first-child { box-shadow: inset 2px 0 0 var(--signal); }
.table .num { color: var(--muted); width: 36px; text-align: right; }
.table .t { color: var(--text); max-width: 0; overflow: hidden; text-overflow: ellipsis; width: 100%; }
.table .t small { color: var(--muted); margin-left: 10px; font-size: 12px; }
.table .dur { color: var(--text-2); text-align: right; }
.check { width: 12px; height: 12px; border: 1px solid var(--rule-strong); display: inline-block; vertical-align: middle; }
.check.on { background: var(--signal); border-color: var(--signal); }

/* ── Dock ─────────────────────────────────────────────────────────────── */
.dock {
grid-column: 1 / -1; display: flex; align-items: center; gap: 16px; padding: 0 12px;
border-top: 1px solid var(--rule); background: var(--g-1); font-size: 12px;
}
.dock .what { color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; flex: 1; }
.dock .sep { width: 1px; height: 20px; background: var(--rule); }
.muted { color: var(--muted); }
.signal { color: var(--signal); }
.row { display: flex; align-items: center; gap: 10px; }
.spacer { flex: 1; }
134 changes: 134 additions & 0 deletions docs/mocks/home.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mock · Home</title>
<link rel="stylesheet" href="console.css">
<style>
.fetch { display: flex; gap: 8px; }
.album { display: grid; grid-template-columns: 168px 1fr; gap: 20px; }
.art { width: 168px; height: 168px; display: block; }
.meta { display: flex; flex-direction: column; gap: 10px; min-width: 0; }
.meta h1 { margin: 0; font-size: 22px; font-weight: 700; letter-spacing: -.01em; line-height: 1.2; }
.meta .by { color: var(--text-2); font-size: 14px; }
.facts { display: flex; color: var(--muted); font-size: 12px; }
.facts span + span::before { content: '·'; padding: 0 8px; color: var(--rule-strong); }
.spec { display: grid; grid-template-columns: repeat(4, auto); justify-content: start; column-gap: 24px; row-gap: 2px; margin-top: 2px; }
.spec .label { font-size: 10px; }
.spec .v { font-size: 12px; color: var(--text); }
.actions { margin-top: auto; display: flex; gap: 8px; align-items: center; }
.sel-count { color: var(--muted); font-size: 12px; margin-left: 8px; }
.table td.q { width: 1%; }
.table td.act { width: 1%; color: var(--muted); }
.btn.primary .key { border-color: rgba(0,0,0,.35); color: inherit; }
</style>
</head>
<body>
<div class="app">
<header class="titlebar">
<span class="wordmark">MORNYE<small>2.0.6</small></span>
<span class="rule"></span>
<span class="state run">● downloading 3 / 12</span>
</header>

<nav class="rail">
<div class="rail-group">
<div class="rail-head">Discover</div>
<a class="on" href="#"><span class="ico">⌂</span>Home</a>
<a href="#"><span class="ico">◇</span>Explore</a>
</div>
<div class="rail-group">
<div class="rail-head">Library</div>
<a href="queue.html"><span class="ico">≡</span>Queue<span class="count">9</span></a>
<a href="#"><span class="ico">↺</span>History</a>
<a href="#"><span class="ico">▶</span>Player</a>
<a href="#"><span class="ico">⌗</span>Local files</a>
<a href="#"><span class="ico">+</span>Following</a>
<a href="#"><span class="ico">▤</span>Server</a>
</div>
<div class="rail-group">
<div class="rail-head">System</div>
<a href="#"><span class="ico">∿</span>Stats</a>
<a href="#"><span class="ico">›</span>Logs</a>
<a href="#"><span class="ico">⚙</span>Settings</a>
</div>
</nav>

<main class="main">
<div class="fetch">
<label class="field"><span class="pre">URL</span><input value="https://open.spotify.com/album/5d1Kc0Q7pX3vXa" aria-label="Link or search"></label>
<button class="btn">Fetch <span class="key">⏎</span></button>
<button class="btn ghost">Search <span class="key">⌘K</span></button>
</div>

<section class="panel">
<span class="legend">Album · <b>Spotify</b></span>
<span class="legend legend-r">Tidal → Qobuz → Deezer</span>
<div class="panel-body album">
<svg class="art" viewBox="0 0 168 168" role="img" aria-label="Cover">
<defs>
<linearGradient id="a" x1="0" y1="0" x2="1" y2="1"><stop offset="0" stop-color="#1d3d6b"/><stop offset="1" stop-color="#c2492b"/></linearGradient>
</defs>
<rect width="168" height="168" fill="url(#a)"/>
<circle cx="112" cy="58" r="34" fill="#f2c14e"/>
<rect y="118" width="168" height="50" fill="#0f1e33" opacity=".85"/>
<path d="M0 118 L40 92 L78 112 L120 80 L168 108 L168 118Z" fill="#0f1e33"/>
</svg>
<div class="meta">
<h1>Night Signals</h1>
<div class="by">Arden Vale</div>
<div class="facts"><span>Album</span><span>2026</span><span>12 tracks</span><span>41:07</span><span>Explicit</span></div>
<div class="row"><span class="qd">24 / 96 · FLAC</span><span class="qd dim">ATMOS</span></div>
<div class="spec">
<span class="label">Label</span><span class="label">UPC</span><span class="label">Released</span><span class="label">Genre</span>
<span class="v">Low Hours Rec.</span><span class="v">0602567891234</span><span class="v">2026-10-03</span><span class="v">Electronic</span>
</div>
<div class="actions">
<button class="btn primary">Download 12</button>
<button class="btn">Download selected</button>
<button class="btn ghost">Copy as CLI</button>
<span class="sel-count">4 selected</span>
</div>
</div>
</div>
</section>

<section class="panel">
<span class="legend">Tracks <b>12</b></span>
<table class="table">
<thead><tr><th></th><th>#</th><th>Title</th><th>Quality</th><th style="text-align:right">Time</th><th>State</th></tr></thead>
<tbody>
<tr class="sel"><td><span class="check on"></span></td><td class="num">1</td><td class="t">Carrier Wave<small>Arden Vale</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">3:42</td><td class="act"><span class="state ok">done</span></td></tr>
<tr class="sel"><td><span class="check on"></span></td><td class="num">2</td><td class="t">Static Bloom<small>Arden Vale, Mira Sol</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">4:05</td><td class="act"><span class="state ok">done</span></td></tr>
<tr class="sel"><td><span class="check on"></span></td><td class="num">3</td><td class="t">Low Hours<small>Arden Vale</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">3:18</td><td class="act"><span class="meter" data-v=".62" data-n="12"></span></td></tr>
<tr class="sel"><td><span class="check on"></span></td><td class="num">4</td><td class="t">Second Antenna<small>Arden Vale</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">5:11</td><td class="act"><span class="state idle">queued</span></td></tr>
<tr><td><span class="check"></span></td><td class="num">5</td><td class="t">Glass Harbour<small>Arden Vale</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">2:57</td><td class="act"></td></tr>
<tr><td><span class="check"></span></td><td class="num">6</td><td class="t">Interference (feat. Kō)<small>Arden Vale, Kō</small></td><td class="q"><span class="qd dim">16 / 44.1</span></td><td class="dur">3:36</td><td class="act"></td></tr>
<tr><td><span class="check"></span></td><td class="num">7</td><td class="t">Northbound Signal Path — Extended Version For The Long Drive<small>Arden Vale</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">7:48</td><td class="act"></td></tr>
<tr><td><span class="check"></span></td><td class="num">8</td><td class="t">Dial Tone<small>Arden Vale</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">3:02</td><td class="act"><span class="state err">not found</span></td></tr>
<tr><td><span class="check"></span></td><td class="num">9</td><td class="t">Quiet Band<small>Arden Vale</small></td><td class="q"><span class="qd dim">24 / 96</span></td><td class="dur">2:49</td><td class="act"></td></tr>
</tbody>
</table>
</section>
</main>

<footer class="dock">
<span class="label">Downloads</span>
<span class="meter lg" data-v=".25" data-n="24"></span>
<span class="signal">3 / 12</span>
<span class="sep"></span>
<span class="what">Low Hours — Arden Vale <span class="muted">· tidal · 4.2 MB/s</span></span>
<span class="qd">24 / 96 · FLAC</span>
<button class="btn ghost">Open queue</button>
</footer>
</div>
<script>
// Segment meters: whole LED steps, never a partial one.
for (const m of document.querySelectorAll('.meter[data-v]')) {
const n = +m.dataset.n, lit = Math.round(+m.dataset.v * n);
m.innerHTML = Array.from({ length: n }, (_, i) => `<i class="${i < lit ? 'on' : ''}"></i>`).join('');
}
</script>
</body>
</html>
Loading
Loading