fix Obsidian reviewer lint errors; add lint to rebuild flow; bump to 1.0.28

- Remove unnecessary type assertions in IconPickerModal (createEl("input") already returns HTMLInputElement)
- Fix sentence-case violation: "Remove GM icon" → "Remove icon" in HexEditorModal tooltip
- Annotate frontmatter["region"] accesses as unknown to satisfy no-unsafe-assignment
- Wire npm run lint into /rebuild command so issues are caught locally before submission
- Document no-unnecessary-type-assertion and mid-sentence acronym patterns in /lint guidance

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
isaprettycoolguy@protonmail.com 2026-05-11 08:49:51 -04:00
parent e7019aaca4
commit 96eef77ffa
10 changed files with 35 additions and 14 deletions

View file

@ -35,6 +35,15 @@ this.registerEvent(
```
This removes `any` entirely; `unknown as SomeInterface` is fully type-safe.
### `@typescript-eslint/no-unnecessary-type-assertion`
A cast (`as T`) where TypeScript already knows the value has type `T`.
**Common cause — `createEl` with a specific tag**: Obsidian's `createEl<K>(tag: K, ...)` returns `HTMLElementTagNameMap[K]`, so `createEl("input", ...)` already returns `HTMLInputElement`. Any subsequent `as HTMLInputElement` cast is redundant.
**Common cause — ambient types not imported**: Using `as DomElementInfo` inline causes ESLint's `no-undef` to fire because the ambient type isn't an explicit import. Drop the cast entirely — `{ type: "checkbox" }` is assignable to `DomElementInfo` without a cast.
**Fix**: Delete the redundant cast. If the type isn't narrow enough, use `as unknown as T` (two-step) rather than a direct `as T` — but prefer removing the cast entirely.
### `obsidianmd/ui/sentence-case`
UI text must use sentence case: only the first word and proper nouns are capitalised.
@ -43,6 +52,7 @@ UI text must use sentence case: only the first word and proper nouns are capital
- `"← Pick icons"``"← pick icons"`
- `"↩ Map mode"``"↩ map mode"`
- Parenthetical range notation: `"(A → Z)"``"(a → z)"`
- Mid-sentence acronyms (e.g. "Remove GM icon"): the rule flags consecutive capitals that are not the first word. Reword to move the acronym to the front, avoid it, or use the full term in lowercase: `"Remove icon"` or `"GM icon: remove"`.
**Fix B — eslint-disable** for intentional special-case UI text that would look wrong if sentence-cased:
- Short abbreviation labels: `"wt ↑"`, `"a→z"`, `"z→a"` — these are symbols/abbreviations, not sentences

View file

@ -1,5 +1,5 @@
---
description: Build the duckmage plugin (type-check + bundle)
description: Build the duckmage plugin (type-check + bundle + lint + tests)
allowed-tools: Bash(npm:*)
---
@ -15,7 +15,17 @@ Report the build result clearly:
- If it succeeded, confirm that `main.js` was updated.
- If it failed, show the full error output and identify the likely cause (TypeScript type error, missing import, syntax error, etc.).
Then, regardless of build result, run the test suite:
Then run the linter:
```
cd /mnt/c/Users/markr/Documents/KB/journal/.obsidian/plugins/duckmage-plugin && npm run lint 2>&1
```
Report the lint result clearly:
- If there are no issues, confirm it is lint-clean.
- If there are errors or warnings, list each one with file, line, rule name, and a brief description. Errors from the Obsidian reviewer are **required** fixes; address them before marking the task done.
Then, regardless of build/lint result, run the test suite:
```
cd /mnt/c/Users/markr/Documents/KB/journal/.obsidian/plugins/duckmage-plugin && npm test

View file

@ -1,7 +1,7 @@
{
"id": "hexmaker",
"name": "Hexmap World Creator",
"version": "1.0.27",
"version": "1.0.28",
"minAppVersion": "1.12.0",
"description": "Create a hex map and a guidebook level wiki for your setting with minimal fuss. Hex crawl worldbuilding toolbox. System agnostic, ready for any TRPG you care to run in it.",
"author": "morkdev",

4
package-lock.json generated
View file

@ -1,12 +1,12 @@
{
"name": "hexmaker-plugin",
"version": "1.0.27",
"version": "1.0.28",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "hexmaker-plugin",
"version": "1.0.27",
"version": "1.0.28",
"license": "MIT",
"dependencies": {
"minisearch": "^7.2.0"

View file

@ -1,6 +1,6 @@
{
"name": "hexmaker-plugin",
"version": "1.0.27",
"version": "1.0.28",
"description": "A plugin to power hex crawl worldbuilding and running.",
"main": "main.js",
"scripts": {

View file

@ -736,7 +736,7 @@ export default class HexmakerPlugin extends Plugin {
let linked = 0;
for (const file of hexFiles) {
const cache = this.app.metadataCache.getFileCache(file);
const region = cache?.frontmatter?.["region"];
const region: unknown = cache?.frontmatter?.["region"];
if (typeof region !== "string" || !region) continue;
await this.syncHexRegionTableLink(file.path, region);
linked++;

View file

@ -512,7 +512,7 @@ export class HexEditorModal extends HexmakerModal {
const clearGmBtn = gmRow.createEl("button", {
text: "Clear",
cls: "duckmage-clear-btn",
title: "Remove GM icon",
title: "Remove icon",
});
clearGmBtn.setCssProps({
visibility: currentGmIcon ? "visible" : "hidden",

View file

@ -2777,7 +2777,7 @@ export class HexMapView extends ItemView {
const file = this.app.vault.getAbstractFileByPath(hexFilePath);
if (!(file instanceof TFile)) return null;
const cache = this.app.metadataCache.getFileCache(file);
const region = cache?.frontmatter?.["region"];
const region: unknown = cache?.frontmatter?.["region"];
return typeof region === "string" ? region : null;
}

View file

@ -39,15 +39,15 @@ export class IconPickerModal extends HexmakerModal {
// GM layer only toggle
const gmRow = headerRight.createDiv({ cls: "duckmage-icon-gm-toggle-row" });
const gmCb = gmRow.createEl("input", { type: "checkbox" } as DomElementInfo);
(gmCb as HTMLInputElement).checked = this.gmOnly;
const gmCb = gmRow.createEl("input", { type: "checkbox" });
gmCb.checked = this.gmOnly;
const gmLabel = gmRow.createSpan({ text: "GM layer only", cls: "duckmage-icon-gm-toggle-label" });
const applyGm = () => {
this.gmOnly = (gmCb as HTMLInputElement).checked;
this.gmOnly = gmCb.checked;
};
gmCb.addEventListener("change", applyGm);
gmLabel.addEventListener("click", () => {
(gmCb as HTMLInputElement).checked = !(gmCb as HTMLInputElement).checked;
gmCb.checked = !gmCb.checked;
applyGm();
});

View file

@ -16,5 +16,6 @@
"1.0.24": "1.12.0",
"1.0.25": "1.12.0",
"1.0.26": "1.12.0",
"1.0.27": "1.12.0"
"1.0.27": "1.12.0",
"1.0.28": "1.12.0"
}