Release 0.27.0: hide-updates filter and consistent Hide-* toggle labels

Adds a 'Hide updates available' toggle to the import dialog (default off; update-available rows are actionable so shown by default). Relabels 'Show trashed' to 'Hide trashed' so all four filter toggles share one polarity (checked = hidden).
This commit is contained in:
Charles Kelsoe 2026-07-08 18:30:14 -04:00
parent 2eeefbcc94
commit 3b055ff3c7
9 changed files with 112 additions and 20 deletions

View file

@ -4,6 +4,16 @@ All notable changes to Plaud Importer will be documented in this file.
## [Unreleased]
## [0.27.0] - 2026-07-08
### Added
- **New "Hide updates available" filter in the import dialog.** A recording you already imported but later changed in Plaud shows an "Update available" badge; re-importing it overwrites the note with Plaud's newer version. Those rows stay visible by default, because they are work you may still want to do. The new "Hide updates available" toggle collapses them too, so the list can show only brand-new recordings when you want the shortest possible list.
### Changed
- **The import dialog's filter toggles now read consistently.** All four are phrased as "Hide ...": "Hide already-processed", "Hide updates available", "Hide ignored", and "Hide trashed" (previously "Show trashed"). A checked box now always means "this is hidden", so the whole bar reads the same way instead of mixing hide and show. Behavior is unchanged: trash is still hidden by default.
## [0.26.0] - 2026-07-08
### Added

View file

@ -32,6 +32,7 @@ function filter(over: Partial<ListViewFilter>): ListViewFilter {
return {
showTrashed: over.showTrashed ?? false,
hideProcessed: over.hideProcessed ?? false,
hideUpdates: over.hideUpdates ?? false,
hideIgnored: over.hideIgnored ?? false,
index: over.index ?? idx([]),
ignoredIds: over.ignoredIds ?? ignored([]),
@ -54,7 +55,7 @@ describe('filterListView', () => {
]);
});
it('always shows a changed recording even when hideProcessed is on', () => {
it('always shows a changed recording even when hideProcessed is on (governed by hideUpdates)', () => {
const index = idx([['a', { path: 'a.md', versionMs: 100 }]]);
const page = [rec({ id: 'a', versionMs: 200 })]; // listed > stored -> update available
expect(ids(filterListView(page, filter({ hideProcessed: true, index })))).toEqual([
@ -62,6 +63,46 @@ describe('filterListView', () => {
]);
});
it('hides a changed recording when hideUpdates is on', () => {
const index = idx([['a', { path: 'a.md', versionMs: 100 }]]);
const page = [
rec({ id: 'a', versionMs: 200 }), // update available -> hidden by hideUpdates
rec({ id: 'b', versionMs: 500 }), // new -> still shown
];
expect(ids(filterListView(page, filter({ hideUpdates: true, index })))).toEqual([
'b',
]);
});
it('hideUpdates does not hide an unchanged processed recording', () => {
const index = idx([['a', { path: 'a.md', versionMs: 100 }]]);
const page = [rec({ id: 'a', versionMs: 100 })]; // unchanged
// hideUpdates on but hideProcessed off -> unchanged import still shows.
expect(
ids(filterListView(page, filter({ hideUpdates: true, hideProcessed: false, index }))),
).toEqual(['a']);
});
it('hideProcessed and hideUpdates together hide every imported row, new still shows', () => {
const index = idx([
['unchanged', { path: 'u.md', versionMs: 100 }],
['changed', { path: 'c.md', versionMs: 100 }],
]);
const page = [
rec({ id: 'unchanged', versionMs: 100 }),
rec({ id: 'changed', versionMs: 200 }),
rec({ id: 'new', versionMs: 500 }),
];
expect(
ids(
filterListView(
page,
filter({ hideProcessed: true, hideUpdates: true, index }),
),
),
).toEqual(['new']);
});
it('always shows a new (not-in-index) recording when hideProcessed is on', () => {
const page = [rec({ id: 'fresh', versionMs: 500 })];
expect(

View file

@ -79,6 +79,12 @@ export interface ImportModalOptions extends NoteWriterOptions {
* field. See `filterListView`.
*/
readonly hideProcessedRecordings?: boolean;
/**
* Hide imported recordings that have changed in Plaud since import. Defaults
* to false: an update is actionable work, so update-available rows show
* unless the user opts to collapse them. A dialog view preference.
*/
readonly hideUpdatesRecordings?: boolean;
/**
* Hide recordings the user has ignored (their id is in `ignoredRecordingIds`).
* Defaults to true. A dialog view preference toggled from the filter bar.
@ -636,6 +642,7 @@ export function isUpdateAvailable(
export interface ImportViewState {
readonly showTrashedRecordings: boolean;
readonly hideProcessedRecordings: boolean;
readonly hideUpdatesRecordings: boolean;
readonly hideIgnoredRecordings: boolean;
readonly ignoredRecordingIds: readonly PlaudRecordingId[];
}
@ -646,8 +653,16 @@ export type ImportViewStatePatch = Partial<ImportViewState>;
export interface ListViewFilter {
/** Show recordings in Plaud's trash. */
readonly showTrashed: boolean;
/** Hide imported-and-unchanged recordings (new/changed still show). */
/** Hide imported recordings that are unchanged since import. */
readonly hideProcessed: boolean;
/**
* Hide imported recordings that have CHANGED in Plaud since import (an
* update is available). Separate from `hideProcessed` because an update is
* actionable work (re-importing overwrites the note with Plaud's newer
* version), so it shows by default; this toggle is the opt-in to collapse
* those too.
*/
readonly hideUpdates: boolean;
/** Hide recordings whose id is in `ignoredIds`. */
readonly hideIgnored: boolean;
/** Vault index (plaud-id -> imported note) used to decide "processed". */
@ -665,10 +680,10 @@ export interface ListViewFilter {
* Order and rules:
* 1. Drop trash unless `showTrashed`.
* 2. Drop ignored ids when `hideIgnored`.
* 3. When `hideProcessed`, drop a recording ONLY if it is in the vault index
* (processed) AND unchanged since import. A new recording (not in the
* index) and a changed one (`isUpdateAvailable`) always show, so the user
* never loses sight of importable or update-available work.
* 3. For an imported recording (present in the index): if it has changed in
* Plaud (`isUpdateAvailable`) it is governed by `hideUpdates`; otherwise it
* is governed by `hideProcessed`. A new recording (not in the index) always
* shows, so importable work is never hidden.
*
* Pure and exported so the view logic is unit-testable without a DOM.
*/
@ -683,12 +698,11 @@ export function filterListView(
if (filter.hideIgnored && filter.ignoredIds.has(r.id)) {
return false;
}
if (filter.hideProcessed) {
const existing = filter.index.get(r.id);
if (
existing !== undefined &&
!isUpdateAvailable(r.versionMs, existing.versionMs)
) {
const existing = filter.index.get(r.id);
if (existing !== undefined) {
// Imported. Split by whether Plaud has a newer version than the note.
const updated = isUpdateAvailable(r.versionMs, existing.versionMs);
if (updated ? filter.hideUpdates : filter.hideProcessed) {
return false;
}
}

View file

@ -472,6 +472,7 @@ export class ImportModal extends Modal {
// per-row membership checks.
private showTrashed: boolean;
private hideProcessed: boolean;
private hideUpdates: boolean;
private hideIgnored: boolean;
private readonly ignoredIds: Set<PlaudRecordingId>;
@ -483,6 +484,8 @@ export class ImportModal extends Modal {
// The two hide-prefs default ON when the host omits them (a fresh install
// or a pre-0.26.0 data.json), matching DEFAULT_SETTINGS.
this.hideProcessed = noteWriterOptions.hideProcessedRecordings !== false;
// Updates default to SHOWN (hide off): an update is actionable work.
this.hideUpdates = noteWriterOptions.hideUpdatesRecordings === true;
this.hideIgnored = noteWriterOptions.hideIgnoredRecordings !== false;
this.ignoredIds = new Set(noteWriterOptions.ignoredRecordingIds ?? []);
this.attachments = new AttachmentImporter({
@ -932,6 +935,7 @@ export class ImportModal extends Modal {
return {
showTrashed: this.showTrashed,
hideProcessed: this.hideProcessed,
hideUpdates: this.hideUpdates,
hideIgnored: this.hideIgnored,
index: this.importedIndex,
ignoredIds: this.ignoredIds,
@ -1008,6 +1012,9 @@ export class ImportModal extends Modal {
// setting through onViewStateChange (so it survives reopen and auto-sync sees
// the same ignore/show state) and re-renders the list in place.
private renderFilterBar(parent: HTMLElement): void {
// All toggles read as "Hide X" so a checked box always means "hidden"
// (consistent polarity). "Show trashed" is expressed as its inverse,
// "Hide trashed", writing the underlying showTrashedRecordings setting.
const bar = parent.createDiv({ cls: 'plaud-importer-filter-bar' });
this.renderFilterToggle(
bar,
@ -1018,13 +1025,22 @@ export class ImportModal extends Modal {
this.persistViewState({ hideProcessedRecordings: checked });
},
);
this.renderFilterToggle(
bar,
'Hide updates available',
this.hideUpdates,
(checked) => {
this.hideUpdates = checked;
this.persistViewState({ hideUpdatesRecordings: checked });
},
);
this.renderFilterToggle(bar, 'Hide ignored', this.hideIgnored, (checked) => {
this.hideIgnored = checked;
this.persistViewState({ hideIgnoredRecordings: checked });
});
this.renderFilterToggle(bar, 'Show trashed', this.showTrashed, (checked) => {
this.showTrashed = checked;
this.persistViewState({ showTrashedRecordings: checked });
this.renderFilterToggle(bar, 'Hide trashed', !this.showTrashed, (checked) => {
this.showTrashed = !checked;
this.persistViewState({ showTrashedRecordings: !checked });
});
}

10
main.ts
View file

@ -471,6 +471,11 @@ interface PlaudImporterSettings {
// dialog filter bar, NOT the settings tab, so it stays out of the
// imperative/declarative settings twin edit.
hideProcessedRecordings: boolean;
// Import-dialog view preference: hide imported recordings that CHANGED in
// Plaud since import (an update is available). Defaults OFF: an update is
// actionable (re-import overwrites the note), so those rows show unless the
// user opts to collapse them too. Dialog-only.
hideUpdatesRecordings: boolean;
// Import-dialog view preference: hide recordings the user has ignored. Also
// dialog-only, defaults on.
hideIgnoredRecordings: boolean;
@ -546,6 +551,7 @@ const DEFAULT_SETTINGS: PlaudImporterSettings = {
writePlaceholderForUnprocessed: true,
showTrashedRecordings: false,
hideProcessedRecordings: true,
hideUpdatesRecordings: false,
hideIgnoredRecordings: true,
ignoredRecordingIds: [],
autoUpdatePlaudTitle: false,
@ -952,6 +958,7 @@ export default class PlaudImporterPlugin extends Plugin {
this.settings.writePlaceholderForUnprocessed,
showTrashedRecordings: this.settings.showTrashedRecordings,
hideProcessedRecordings: this.settings.hideProcessedRecordings,
hideUpdatesRecordings: this.settings.hideUpdatesRecordings,
hideIgnoredRecordings: this.settings.hideIgnoredRecordings,
// data.json stores plain strings; PlaudRecordingId is a compile-time
// brand over string. Re-tag each id at this boundary (a per-element
@ -2342,6 +2349,9 @@ export default class PlaudImporterPlugin extends Plugin {
if (patch.hideProcessedRecordings !== undefined) {
this.settings.hideProcessedRecordings = patch.hideProcessedRecordings;
}
if (patch.hideUpdatesRecordings !== undefined) {
this.settings.hideUpdatesRecordings = patch.hideUpdatesRecordings;
}
if (patch.hideIgnoredRecordings !== undefined) {
this.settings.hideIgnoredRecordings = patch.hideIgnoredRecordings;
}

View file

@ -1,7 +1,7 @@
{
"id": "plaud-importer",
"name": "Plaud Importer",
"version": "0.26.0",
"version": "0.27.0",
"minAppVersion": "1.11.4",
"description": "Import meeting summaries, transcripts, and attachments from Plaud.AI into your vault.",
"author": "Charles Kelsoe (ckelsoe)",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "obsidian-plaud-importer",
"version": "0.26.0",
"version": "0.27.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "obsidian-plaud-importer",
"version": "0.26.0",
"version": "0.27.0",
"license": "MIT",
"devDependencies": {
"@eslint/js": "^9.39.2",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-plaud-importer",
"version": "0.26.0",
"version": "0.27.0",
"description": "Import meeting summaries, transcripts, and attachments from Plaud.AI into an Obsidian vault.",
"main": "main.js",
"scripts": {

View file

@ -47,5 +47,6 @@
"0.25.0": "1.11.4",
"0.25.1": "1.11.4",
"0.25.2": "1.11.4",
"0.26.0": "1.11.4"
"0.26.0": "1.11.4",
"0.27.0": "1.11.4"
}