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
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

## [Unreleased]

## [0.11.0] - 2026-09-16

### Changed

- Refined the recording library into compact two-line rows with regular-weight
truncated titles, trailing semantic status badges, flatter selection,
subtle inset separators, quieter progressive quick-play actions, a narrow
scrollbar, and playback controls free of the redundant persistent
network-use reminder. Refreshed the shared neutral palette to cleaner white
and cool-neutral surfaces with stronger secondary-text legibility.

## [0.10.0] - 2026-09-15

### Added
Expand Down Expand Up @@ -395,7 +406,8 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht
- Added GitHub Actions workflows for continuous integration and tagged Windows releases.
- Added English and Simplified Chinese README documentation.

[Unreleased]: https://github.com/kwp-lab/nota/compare/v0.10.0...HEAD
[Unreleased]: https://github.com/kwp-lab/nota/compare/v0.11.0...HEAD
[0.11.0]: https://github.com/kwp-lab/nota/compare/v0.10.0...v0.11.0
[0.10.0]: https://github.com/kwp-lab/nota/compare/v0.9.0...v0.10.0
[0.9.0]: https://github.com/kwp-lab/nota/compare/v0.8.0...v0.9.0
[0.8.0]: https://github.com/kwp-lab/nota/compare/v0.7.0...v0.8.0
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<p align="center">
<img alt="Platform: Windows 11 x64" src="https://img.shields.io/badge/platform-Windows%2011%20x64-0B6AA2?style=flat-square&logo=windows11&logoColor=white">
<img alt="Version: 0.10.0" src="https://img.shields.io/badge/version-0.10.0-56615D?style=flat-square">
<img alt="Version: 0.11.0" src="https://img.shields.io/badge/version-0.11.0-56615D?style=flat-square">
<img alt="Status: early preview" src="https://img.shields.io/badge/status-early%20preview-C18B48?style=flat-square">
<img alt="Privacy: local first" src="https://img.shields.io/badge/privacy-local%20first-2F7D71?style=flat-square">
<img alt="Telemetry: none" src="https://img.shields.io/badge/telemetry-none-2F7D71?style=flat-square">
Expand Down
2 changes: 1 addition & 1 deletion README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

<p align="center">
<img alt="平台:Windows 11 x64" src="https://img.shields.io/badge/platform-Windows%2011%20x64-0B6AA2?style=flat-square&logo=windows11&logoColor=white">
<img alt="版本:0.10.0" src="https://img.shields.io/badge/version-0.10.0-56615D?style=flat-square">
<img alt="版本:0.11.0" src="https://img.shields.io/badge/version-0.11.0-56615D?style=flat-square">
<img alt="状态:早期预览" src="https://img.shields.io/badge/status-early%20preview-C18B48?style=flat-square">
<img alt="隐私:本地优先" src="https://img.shields.io/badge/privacy-local%20first-2F7D71?style=flat-square">
<img alt="遥测:无" src="https://img.shields.io/badge/telemetry-none-2F7D71?style=flat-square">
Expand Down
2 changes: 1 addition & 1 deletion THIRD_PARTY_NOTICES.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4242,7 +4242,7 @@ developed by Chen Jiaju, licensed under the MIT License and the Apache License 2
-------------------------------------------------------------------------------
MIT License (MIT)
Used by:
- nota 0.10.0
- nota 0.11.0
- brotli-decompressor 5.0.3
- cargo_toml 0.22.3
- dpi 0.1.2
Expand Down
4 changes: 2 additions & 2 deletions bom.cyclonedx.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"serialNumber": "urn:uuid:2e51a386-9432-4a50-a3c3-0e7d998d7106",
"serialNumber": "urn:uuid:30a8310e-8b01-43a9-a4ca-542abf91333f",
"version": 1,
"metadata": {
"component": {
"type": "application",
"bom-ref": "pkg:github/kwp-lab/nota",
"name": "Nota",
"version": "0.10.0",
"version": "0.11.0",
"licenses": [
{
"license": {
Expand Down
24 changes: 22 additions & 2 deletions docs/design-system.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ It is intentionally separate from marketing artwork and release assets.
## Product Character

Nota should feel calm, trustworthy, local, and precise. The visual language
uses warm neutral surfaces and a restrained jade accent so recording state and
uses clean neutral surfaces and a restrained jade accent so recording state and
meeting content remain more prominent than decoration. Interfaces should feel
native to Windows 11 without impersonating a Microsoft product.

Expand Down Expand Up @@ -225,6 +225,25 @@ available when the list is hidden.
The compact title, detail tabs, contextual toolbar, reading region, and native
audio player follow DOM order. The list and reading region scroll independently;
the outer detail pane must not scroll. Playback remains mounted across tabs.
The recording list uses compact two-line rows rather than individual cards. A
row contains a regular-weight, single-line recording title followed by a
two-ended metadata line: date and duration stay at the leading edge, while a
compact semantic transcription badge aligns to the trailing edge. Long titles
truncate with an ellipsis without displacing the fixed quick-play action; the
full title remains available to assistive technology and on pointer hover. File
size remains available in the selected recording header instead of being
repeated throughout the list. Status badges use Caption text, a small radius,
semantic foreground/surface colors, and a subtle border instead of relying on
text color alone. Quiet inter-row spacing, comfortable vertical padding, and
subtle inset hairline separators establish rhythm without turning rows into
individual cards or restoring a permanent leading icon. Separators sit between
rows and disappear when a row is hovered, selected, or owns an open context
menu so the interactive surface reads as one unit. The selected row uses a
flat brand surface. The quick-play action appears for hover, keyboard focus,
selection, or active playback, and Space toggles playback for the focused row.
Its compact trailing slot remains reserved so row content does not shift when
the action appears. The list scrollbar uses a narrow neutral thumb on a
transparent track while retaining a visible high-contrast fallback.

AI documents use shared select styling for document/version selection instead
of a permanent nested document sidebar. The document selector may grow to a
Expand Down Expand Up @@ -268,7 +287,8 @@ At 1280×800 and 980×640, normal completed-document content targets at least
that area. Controls must not wrap into multiple toolbar rows, and long titles,
models, and paths must not cause page-level horizontal overflow. Use existing
semantic tokens; do not shrink body type to achieve density. The playback
footer retains the local-recording/network-use reminder.
footer contains only playback controls. Network-use disclosure belongs at the
transcription or AI-generation action boundary, not beneath local playback.

## Change and Review Checklist

Expand Down
15 changes: 15 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,21 @@ Automated coverage must include:
dismissal, focus restoration, and an accessible name for every option;
- PID rebinding by executable path and no silent switch to another application.

## Recording Library Visual Regression Matrix

Automated coverage and Windows acceptance must include:

- compact two-line rows that keep title, date, duration, and transcription
state readable without repeating file size or a leading recording icon;
- quick playback exposed on row hover, keyboard focus, selection, and active
playback, with Space toggling playback while Enter keeps its selection role;
- flat selected, context-menu, processing, completed, and failed states with
visible focus and no card border or elevation;
- an independently scrolling list with a narrow neutral scrollbar, transparent
track, and usable forced-colors behavior;
- a playback footer with no persistent network-use note, verified at 100%,
125%, and 150% Windows scaling and at the 980×640 minimum window size.

## Audio Import Regression Matrix

Automated coverage must include:
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "nota",
"private": true,
"version": "0.10.0",
"version": "0.11.0",
"license": "MIT",
"repository": {
"type": "git",
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 = "nota"
version = "0.10.0"
version = "0.11.0"
description = "A private, local-first Windows meeting recorder"
authors = ["Nota Contributors"]
license = "MIT"
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": "Nota",
"version": "0.10.0",
"version": "0.11.0",
"identifier": "com.local.meetingnote",
"build": {
"beforeDevCommand": "npm run dev:web",
Expand Down
41 changes: 41 additions & 0 deletions src/components/RecordingsWorkspace.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,47 @@ describe("RecordingsWorkspace", () => {
expect(screen.getByText("客户访谈")).toBeInTheDocument();
});

it("renders recordings as compact two-line rows without redundant list chrome", async () => {
const actions = renderWorkspace();
await waitFor(() => expect(actions.onPreparePlayback).toHaveBeenCalled());

const list = screen.getByLabelText("录音列表");
const row = within(list).getByText("产品周会").closest("article")!;
const metadata = row.querySelector(".history-item-meta");
const title = within(row).getByText("产品周会");
const status = within(row).getByText("已转写");

expect(screen.getByLabelText("2 条录音")).toHaveTextContent("2 条");
expect(row.querySelector(".history-item-icon")).toBeNull();
expect(row.querySelector(".history-status-dot")).toBeNull();
expect(title).toHaveClass("history-item-title");
expect(title).toHaveAttribute("title", "产品周会");
expect(metadata?.firstElementChild).toHaveClass("history-item-facts");
expect(metadata?.lastElementChild).toBe(status);
expect(status).toHaveClass("transcription-badge", "history-transcription-status", "status-completed");
expect(metadata).toHaveTextContent("1:02");
expect(metadata).toHaveTextContent("已转写");
expect(within(list).queryByText("1.0 MB")).not.toBeInTheDocument();
expect(screen.queryByText(/本地录音;仅在转写或手动生成 AI 文档时连接所选服务/)).not.toBeInTheDocument();
});

it("plays the focused recording with Space while Enter remains selection", async () => {
const play = vi.spyOn(HTMLMediaElement.prototype, "play").mockResolvedValue();
const actions = renderWorkspace();
await waitFor(() => expect(document.querySelector("audio")).toHaveAttribute(
"src",
`asset://${completed.path}`,
));
const select = within(screen.getByLabelText("录音列表"))
.getByText("产品周会")
.closest("button")!;

fireEvent.keyDown(select, { key: "Enter" });
expect(play).not.toHaveBeenCalled();
fireEvent.keyDown(select, { key: " " });
expect(play).toHaveBeenCalledOnce();
});

it("shows provider speaker labels and timestamp controls without inventing roles", () => {
renderWorkspace();
expect(screen.getByText("speaker_1")).toBeInTheDocument();
Expand Down
47 changes: 28 additions & 19 deletions src/components/RecordingsWorkspace.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { convertFileSrc } from "@tauri-apps/api/core";
import {
AlertCircle,
Check,
Clipboard,
Download,
FileAudio,
Expand Down Expand Up @@ -707,7 +706,9 @@ export function RecordingsWorkspace(props: RecordingsWorkspaceProps) {
<FileUp size={14} />导入录音
</button>
</AppTooltip>
<span className="count-pill">{props.items.length}</span>
<span className="history-count" aria-label={`${props.items.length} 条录音`}>
{props.items.length} 条
</span>
</div>
</div>
<label className="history-search">
Expand Down Expand Up @@ -826,29 +827,38 @@ export function RecordingsWorkspace(props: RecordingsWorkspaceProps) {
filtered.map((item) => (
<article
key={item.id}
className={`history-item ${item.id === props.selectedId ? "selected" : ""} ${item.id === actionMenu?.recordingId ? "menu-target" : ""}`}
className={`history-item ${item.id === props.selectedId ? "selected" : ""} ${playing && selected?.id === item.id ? "is-playing" : ""} ${item.id === actionMenu?.recordingId ? "menu-target" : ""}`}
onContextMenu={(event) => {
event.preventDefault();
openContextMenu(item, event.clientX, event.clientY);
}}
>
<button className="history-select" onClick={() => props.onSelect(item.id)}>
<span className="history-item-icon">
{item.transcription?.status === "completed" ? <Check size={15} /> : <FileAudio size={15} />}
</span>
<button
className="history-select"
aria-keyshortcuts="Space"
onClick={() => props.onSelect(item.id)}
onDoubleClick={() => toggleQuickPlayback(item)}
onKeyDown={(event) => {
if (event.key !== " ") return;
event.preventDefault();
toggleQuickPlayback(item);
}}
>
<span className="history-item-main">
<strong>{item.title}</strong>
<small>
{new Date(item.createdAt).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
})}
{" · "}{formatDuration(item.durationMs)}{" · "}{formatSize(item.sizeBytes)}
<span className="history-item-title" title={item.title}>{item.title}</span>
<small className="history-item-meta">
<span className="history-item-facts">
{new Date(item.createdAt).toLocaleDateString("zh-CN", {
month: "short",
day: "numeric",
})}
{" · "}{formatDuration(item.durationMs)}
</span>
<span className={`transcription-badge history-transcription-status status-${item.transcription?.status ?? "none"}`}>
{item.transcription ? transcriptionLabel(item.transcription) : "未转写"}
{item.transcription ? transcriptionProgressSuffix(item.transcription) : ""}
</span>
</small>
<span className={`transcription-badge status-${item.transcription?.status ?? "none"}`}>
{item.transcription ? transcriptionLabel(item.transcription) : "未转写"}
{item.transcription ? transcriptionProgressSuffix(item.transcription) : ""}
</span>
</span>
</button>
<AppTooltip content={playing && selected?.id === item.id ? "暂停" : "播放"} side="left">
Expand Down Expand Up @@ -1194,7 +1204,6 @@ export function RecordingsWorkspace(props: RecordingsWorkspaceProps) {
}}
/>
</div>
<div className="record-privacy-note">本地录音;仅在转写或手动生成 AI 文档时连接所选服务</div>
</>
)}
</article>
Expand Down
Loading