fix: address Obsidian community plugin review findings (1.1.1)

- manifest: drop redundant "Obsidian" from description
- settings: render logo as inline themed SVG (no shipped image assets,
  adapts via currentColor), move all inline styles to styles.css, use
  Setting().setHeading() for section headers, drop document/activeDocument
  access, and avoid returning a promise from a void event handler
- syncInbound: use FileManager.trashFile to respect the user's deletion
  preference instead of Vault.trash
- main: type catch parameters as unknown (no unsafe any)
- apiClient: optional catch binding for the unused error
- build: replace builtin-modules package with node:module builtinModules
- release CI: attach only main.js/manifest.json/styles.css and add
  build provenance attestations
- versions.json: backfill 1.0.1 and 1.1.1

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dominik Bartosik 2026-06-15 03:00:16 +02:00
parent ffd5b07ec5
commit a05c1252d1
12 changed files with 128 additions and 77 deletions

View file

@ -1,9 +1,9 @@
name: Release Obsidian Plugin
# Triggered by pushing a SemVer tag (v*). Builds the production bundle
# and attaches main.js, manifest.json, and logo SVGs to the GitHub
# Release - the same artefacts the Obsidian Community Plugins store
# expects to download from the GitHub release.
# and attaches only the files Obsidian downloads - main.js, manifest.json,
# and styles.css - to the GitHub Release. Obsidian ignores any other
# release assets, so none are attached.
#
# The Release is published under the BARE version tag (e.g. 1.1.0), not
# the pushed v-prefixed tag. Obsidian matches manifest.json "version"
@ -17,6 +17,8 @@ on:
permissions:
contents: write
id-token: write
attestations: write
jobs:
build-and-release:
@ -65,9 +67,16 @@ jobs:
- name: Collect release artefacts
run: |
mkdir -p release
cp main.js manifest.json logo-dark.svg logo-light.svg release/
cp main.js manifest.json release/
if [ -f styles.css ]; then cp styles.css release/; fi
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: |
release/main.js
release/manifest.json
- name: Publish GitHub Release
uses: softprops/action-gh-release@v2
with:
@ -78,8 +87,6 @@ jobs:
files: |
release/main.js
release/manifest.json
release/logo-dark.svg
release/logo-light.svg
release/styles.css
fail_on_unmatched_files: false
env:

View file

@ -51,8 +51,8 @@ Two-way OAuth sync between your Obsidian vault and your Unabyss memory.
pnpm build
```
3. Copy `main.js`, `manifest.json`, `logo-dark.svg`, and
`logo-light.svg` into `<vault>/.obsidian/plugins/unabyss/`
3. Copy `main.js`, `manifest.json`, and `styles.css` into
`<vault>/.obsidian/plugins/unabyss/`
(create the folder if it doesn't exist).
4. Enable the plugin in Settings -> Community plugins.

View file

@ -1,6 +1,6 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
import { builtinModules } from "module";
const banner = `/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
@ -28,7 +28,8 @@ const context = await esbuild.context({
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins,
...builtinModules,
...builtinModules.map((name) => `node:${name}`),
],
format: "cjs",
target: "es2018",

View file

@ -1,9 +1,9 @@
{
"id": "unabyss",
"name": "Unabyss",
"version": "1.1.0",
"version": "1.1.1",
"minAppVersion": "1.5.0",
"description": "Sync notes and exports between your Obsidian vault and Unabyss.",
"description": "Sync notes and exports between your vault and Unabyss.",
"author": "Unabyss",
"authorUrl": "https://unabyss.com",
"isDesktopOnly": false

View file

@ -21,7 +21,6 @@
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.11.0",
"builtin-modules": "^3.3.0",
"esbuild": "^0.21.5",
"jest": "^29.7.0",
"obsidian": "^1.5.7",

View file

@ -14,9 +14,6 @@ importers:
'@types/node':
specifier: ^20.11.0
version: 20.19.41
builtin-modules:
specifier: ^3.3.0
version: 3.3.0
esbuild:
specifier: ^0.21.5
version: 0.21.5
@ -575,10 +572,6 @@ packages:
buffer-from@1.1.2:
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
builtin-modules@3.3.0:
resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==}
engines: {node: '>=6'}
callsites@3.1.0:
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
engines: {node: '>=6'}
@ -2020,8 +2013,6 @@ snapshots:
buffer-from@1.1.2: {}
builtin-modules@3.3.0: {}
callsites@3.1.0: {}
camelcase@5.3.1: {}

View file

@ -151,7 +151,7 @@ export class UnabyssApiClient {
}
try {
await this.refreshTokens();
} catch (err) {
} catch {
await this.clearAuth("refresh_failed");
throw new AuthExpiredError();
}

View file

@ -232,14 +232,14 @@ export default class UnabyssPlugin extends Plugin {
this.settings.outboundEnabled
? this.runOutboundSync(true)
.then((report) => ({ ok: true as const, report }))
.catch((err) => ({ ok: false as const, err }))
.catch((err: unknown) => ({ ok: false as const, err }))
: Promise.resolve(null);
const inboundP: Promise<DirectionOutcome<SyncInboundReport> | null> =
this.settings.inboundEnabled
? this.runInboundSync(true)
.then((report) => ({ ok: true as const, report }))
.catch((err) => ({ ok: false as const, err }))
.catch((err: unknown) => ({ ok: false as const, err }))
: Promise.resolve(null);
const [outboundSettled, inboundSettled] = await Promise.allSettled([outboundP, inboundP]);

View file

@ -61,29 +61,42 @@ export class UnabyssSettingTab extends PluginSettingTab {
private renderHeader(containerEl: HTMLElement): void {
const header = containerEl.createDiv({ cls: "unabyss-settings-header" });
header.style.display = "flex";
header.style.alignItems = "center";
header.style.gap = "10px";
header.style.marginBottom = "18px";
const logo = header.createEl("img", { cls: "unabyss-settings-logo" });
logo.alt = "Unabyss";
logo.src = this.pluginLogoResourcePath();
logo.style.width = "32px";
logo.style.height = "32px";
logo.style.flexShrink = "0";
const title = header.createEl("h2", { text: "Unabyss" });
title.style.margin = "0";
this.renderLogo(header);
header.createDiv({ cls: "unabyss-settings-title", text: "Unabyss" });
}
private pluginLogoResourcePath(): string {
const logoFile = document.body.classList.contains("theme-dark")
? "logo-dark.svg"
: "logo-light.svg";
return this.app.vault.adapter.getResourcePath(
`.obsidian/plugins/${this.plugin.manifest.id}/${logoFile}`,
);
/**
* Renders the Unabyss mark as inline SVG that inherits the current
* theme text colour, so the plugin ships no extra image assets and
* adapts to light/dark automatically.
*/
private renderLogo(parent: HTMLElement): void {
const dotOpacities = [
0.28, 0.26, 0.89, 0.65,
0.56, 0.7, 0.88, 0.69,
0.57, 0.26, 0.62, 0.5,
0.74, 0.08, 0.13, 0.98,
];
const coords = [4, 12, 20, 28];
const svg = parent.createSvg("svg", {
cls: "unabyss-settings-logo",
attr: { viewBox: "0 0 32 32", width: 32, height: 32 },
});
let index = 0;
for (const cy of coords) {
for (const cx of coords) {
svg.createSvg("circle", {
attr: {
cx,
cy,
r: 3,
fill: "currentColor",
"fill-opacity": dotOpacities[index],
},
});
index++;
}
}
}
private renderApiBaseUrl(containerEl: HTMLElement): void {
@ -111,11 +124,6 @@ export class UnabyssSettingTab extends PluginSettingTab {
}
const auth = this.plugin.settings.auth;
const banner = containerEl.createDiv({ cls: "unabyss-connection-banner" });
banner.style.marginBottom = "16px";
banner.style.padding = "12px 14px";
banner.style.borderRadius = "8px";
banner.style.border = "1px solid var(--background-modifier-border)";
banner.style.background = "var(--background-secondary)";
banner.createEl("p", {
text:
@ -128,11 +136,7 @@ export class UnabyssSettingTab extends PluginSettingTab {
cls: "setting-item-description",
});
const actions = banner.createDiv();
actions.style.display = "flex";
actions.style.flexWrap = "wrap";
actions.style.gap = "8px";
actions.style.marginTop = "10px";
const actions = banner.createDiv({ cls: "unabyss-connection-banner-actions" });
const syncBtn = actions.createEl("button", { text: "Sync now" });
syncBtn.classList.add("mod-cta");
@ -221,7 +225,7 @@ export class UnabyssSettingTab extends PluginSettingTab {
}
private renderOutboundSection(containerEl: HTMLElement): void {
containerEl.createEl("h3", { text: "Obsidian \u2192 Unabyss (outbound)" });
new Setting(containerEl).setName("Obsidian \u2192 Unabyss (outbound)").setHeading();
new Setting(containerEl)
.setName("Sync outbound")
@ -267,7 +271,7 @@ export class UnabyssSettingTab extends PluginSettingTab {
}
private renderInboundSection(containerEl: HTMLElement): void {
containerEl.createEl("h3", { text: "Unabyss \u2192 Obsidian (inbound)" });
new Setting(containerEl).setName("Unabyss \u2192 Obsidian (inbound)").setHeading();
new Setting(containerEl)
.setName("Sync inbound")
@ -322,7 +326,7 @@ export class UnabyssSettingTab extends PluginSettingTab {
}
private renderAdvancedSection(containerEl: HTMLElement): void {
containerEl.createEl("h3", { text: "Advanced" });
new Setting(containerEl).setName("Advanced").setHeading();
this.renderApiBaseUrl(containerEl);
new Setting(containerEl)
.setName("Force full resync")
@ -359,26 +363,16 @@ export class UnabyssSettingTab extends PluginSettingTab {
return;
}
const chipsRow = containerEl.createDiv({ cls: "unabyss-folder-chip-row" });
chipsRow.style.display = "flex";
chipsRow.style.flexWrap = "wrap";
chipsRow.style.gap = "8px";
chipsRow.style.marginBottom = "12px";
for (const folder of folders) {
const chip = chipsRow.createDiv({ cls: "unabyss-folder-chip" });
chip.style.padding = "4px 8px";
chip.style.border = "1px solid var(--background-modifier-border)";
chip.style.borderRadius = "4px";
chip.style.display = "flex";
chip.style.alignItems = "center";
chip.style.gap = "6px";
chip.createSpan({ text: folder });
const remove = chip.createEl("button", { text: "x" });
remove.style.background = "transparent";
remove.style.border = "none";
remove.style.cursor = "pointer";
remove.addEventListener("click", async () => {
const remove = chip.createEl("button", {
text: "x",
cls: "unabyss-folder-chip-remove",
});
remove.addEventListener("click", () => {
const next = folders.filter((entry) => entry !== folder);
await save(next);
void save(next);
});
}
}

View file

@ -205,7 +205,7 @@ async function applyDeletion(
};
}
if (deps.deleteBehaviour === "delete") {
await deps.app.vault.trash(existing, true);
await deps.app.fileManager.trashFile(existing);
return {
exportId: row.id,
title: row.title,

56
styles.css Normal file
View file

@ -0,0 +1,56 @@
.unabyss-settings-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 18px;
}
.unabyss-settings-logo {
width: 32px;
height: 32px;
flex-shrink: 0;
color: var(--text-normal);
}
.unabyss-settings-title {
margin: 0;
font-size: var(--font-ui-large);
font-weight: var(--font-bold);
}
.unabyss-connection-banner {
margin-bottom: 16px;
padding: 12px 14px;
border-radius: 8px;
border: 1px solid var(--background-modifier-border);
background: var(--background-secondary);
}
.unabyss-connection-banner-actions {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-top: 10px;
}
.unabyss-folder-chip-row {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 12px;
}
.unabyss-folder-chip {
padding: 4px 8px;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
display: flex;
align-items: center;
gap: 6px;
}
.unabyss-folder-chip-remove {
background: transparent;
border: none;
cursor: pointer;
}

View file

@ -1,4 +1,7 @@
{
"0.1.0": "1.5.0",
"1.0.0": "1.5.0"
"1.0.0": "1.5.0",
"1.0.1": "1.5.0",
"1.1.0": "1.5.0",
"1.1.1": "1.5.0"
}