rimossi i file di sviluppo dalla repo

This commit is contained in:
gabriele-cusato 2026-03-25 12:56:08 +01:00
parent 208703b273
commit cffaef0d69
6 changed files with 0 additions and 914 deletions

View file

@ -1,15 +0,0 @@
{
"permissions": {
"allow": [
"WebSearch",
"Bash(cd \"C:/Projects/pluginObsidian/handWrittenMarkdownConverter/obsidian-sample-plugin\" && node esbuild.config.mjs production && bash cloudDeploy.sh)",
"WebFetch(domain:groups.google.com)",
"WebFetch(domain:css-tricks.com)",
"WebFetch(domain:chromium.googlesource.com)",
"WebFetch(domain:github.com)",
"WebFetch(domain:developer.chrome.com)",
"Bash(\"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\" --login -c \"cd 'C:/Projects/pluginObsidian/handWrittenMarkdownConverter/HandTranscriptMd' && bash deploy.sh && bash cloudDeploy.sh\" 2>&1)",
"Bash(\"C:\\\\Program Files\\\\Git\\\\bin\\\\bash.exe\" --login -c \"cd 'C:/Projects/pluginObsidian/handWrittenMarkdownConverter/HandTranscriptMd' && bash deploy.sh\" 2>&1)"
]
}
}

235
CLAUDE.md
View file

@ -1,235 +0,0 @@
# Obsidian Handwriting Plugin — Contesto per Claude Code
## Chi sono / Setup esistente
- Sviluppatore IT con esperienza in C#, JavaScript, TypeScript, Python, SQL Server, VB6
- Vault Obsidian organizzato: `Project/CLIENTI/NOME_CLIENTE/Progetto/`
- Sottocartella `_docs/` sincronizzata via **Google Drive**
- Struttura: `01_Riunioni/`, `02_Documentazione/`, `03_UI_Diagrammi/`, `DECISIONS.md`, `IDEAS.md`, `TODO.md`
- Su PC usa **Claude Code** che legge i file markdown direttamente
- Tablet Android con pennino (in valutazione acquisto)
---
## Obiettivo: Plugin Obsidian "Handwriting to Markdown"
### Cosa voglio
Un plugin Obsidian che inserisce un **riquadro canvas inline** in un file `.md` dove posso:
1. **Scrivere a mano con il pennino** (su tablet Android)
2. Il testo scritto viene **convertito automaticamente in markdown strutturato**:
- `# testo` → H1, `## testo` → H2, `### testo` → H3
- `- testo` → lista, `1. testo` → lista numerata
- `- [ ] testo` → checkbox, `- [x]` → checkbox spuntata
- `> testo` → blockquote
- `` `testo` `` → codice inline, ` ```js ... ``` ` → blocco codice
- `==testo==` → highlight, `**testo**` → grassetto, `*testo*` → corsivo
- `~~testo~~` → barrato, `---` → separatore
3. Il risultato è **testo markdown puro** inserito nel file `.md` esistente
4. I disegni/schemi restano come immagine SVG linkati nel markdown
5. **Bidirezionale**: modificabile sia da tablet che da PC
6. **Nessun formato proprietario** — i file devono essere leggibili anche senza il plugin installato
### Requisiti tecnici
- Funziona su **Android** (Obsidian Mobile) e **Windows**
- Sync via **Google Drive** (già configurato)
- I file `.md` devono restare leggibili da **Claude Code** su PC
- Preferenza per soluzioni **senza dipendenze cloud** dove possibile
---
## Stato attuale — Fase 6 (pannello portale inline + bgMode live update + fix CSS tema) — TESTATO
### File del plugin (`HandTranscriptMd/src/`)
| File | Cosa fa |
|------|---------|
| `main.ts` | Entry point: registra embed, editor view, comando, ribbon, settings, `previewCallbacks` per sync inline↔tab |
| `settings.ts` | Impostazioni: cartella SVG, dimensioni canvas, sfondo, lingue OCR, chiave API Gemini, toggle `hwmHandwritingMode`; mostra versione (`plugin.manifest.version`) e branch (`PLUGIN_BRANCH` costante hardcoded) nell'header della pagina impostazioni |
| `drawing-canvas.ts` | Motore disegno Canvas API: Bézier quadratiche, penna, gomma parziale, undo/redo history-based, auto-expand, righe foglio, `allowFingerScroll()` |
| `svg-utils.ts` | Conversione tratti ↔ SVG (dati riedit in `<desc>` JSON), righe e sfondo inclusi nell'SVG |
| `embed.ts` | Code block processor → preview SVG inline + pannello portale (figlio dello span, position:absolute); click matita → Modal (Windows) o nuova tab (Android); badge mode; `onBgModeRemap` per aggiornare SVG al cambio tema |
| `editor-view.ts` | `DrawingEditorView` (tab dedicata, Android) + `DrawingModal` (overlay fullscreen, Windows), toolbar completa, auto-save |
| `recognizer.ts` | `IRecognizer` interface + `GeminiRecognizer`: invia PNG base64 a Gemini API, restituisce testo |
| `md-parser.ts` | `parseMarkdown()`: post-processa testo OCR riga per riga applicando sintassi markdown |
### Architettura attuale (Fase 3+)
**Due formati supportati:**
- **NUOVO (default)**: `![[_handwriting/hw_xxx.svg]]` — SVG visibile nativamente anche senza plugin
- **LEGACY**: `` ```handwriting {"id":..., "svg":...}``` `` — vecchio formato, mantenuto per compatibilità
**Preview inline — Nuovo formato wiki:**
- Obsidian renderizza `![[svg]]` come `<span class="internal-embed image-embed">[img][/img]</span>`
- **MutationObserver su `document.body`** intercetta gli span con `src*="_handwriting/"` non appena appaiono nel DOM (funziona sia in reading view che in live preview dove il post-processor non viene chiamato)
- `tryDecorate()` con flag `data-hwm-decorated="1"` per evitare doppia elaborazione; ritenta dopo 150ms se la classe `image-embed` non è ancora presente (caricamento asincrono)
- **Classe badge mode**: `span.classList.toggle('hwm-badge-mode', hwmHandwritingMode)` — unica modifica allo span quando lo switch è attivo
- **Pannello portale** (`hwm_portal-panel`) con 4 bottoni: ✏️ (apre Modal su Windows / tab su Android), 📄 (converti OCR), ↕️ (comprimi/espandi), ✕ (elimina).
- **Posizione**: `container.appendChild(panel)` — il pannello è un figlio diretto dello span; `position: absolute; top: 6px; right: 6px` ancorato allo span (che ha `position: relative`). Non più in `document.body`.
- **Nessun RAF loop**: eliminato il loop `requestAnimationFrame` e il listener `scroll`. Il pannello segue lo span naturalmente nel DOM.
- **Cleanup**: `plugin.register(() => panel.remove())` — il pannello viene rimosso quando il plugin viene disabilitato.
- **Click dei bottoni**: lo span ha `pointer-events: none` (per handwriting Android); il pannello ha `pointer-events: auto` in CSS per ripristinare i click.
- **Nascondere con modal aperto**: Desktop → `panel.style.display = 'none'` nel click handler, ripristinato nel callback `modal.onClosed`; Mobile → `workspace.on('layout-change', ...)` per rilevare apertura/chiusura tab.
- **Comprimi/Espandi**: usa `container.style.height + overflow: hidden` sullo span contenitore — modifica solo l'altezza visibile senza toccare la larghezza dell'immagine.
- **Refresh immagine**: `previewCallbacks.set(embedId, ...)` aggiorna `img.src` con cache-bust `?t=timestamp` dopo ogni salvataggio dalla tab editor (Obsidian non aggiorna automaticamente `![[svg]]` in live preview quando il file cambia)
- **NO data-URI**: il src dell'img resta sempre l'URL vault (`http://localhost/_capacitor_file_/...`) — un data-URI verrebbe interpretato da Android come drawing surface
**Preview inline — Formato legacy:**
- Mostra l'SVG come CSS `background-image` su un `<div>` (no `<img>`)
- 3 bottoni inline (`<div role="button">`, non `<button>`) dentro il container del code block
- Bottone portale singolo (`hwm_portal-btn`, cerchio matita) in `document.body` per aprire la tab editor
**Editor disegno — due modalità (`editor-view.ts`):**
`DrawingEditorView extends ItemView` — usato su **Android**:
- Canvas in un DOM completamente separato da CodeMirror → **nessun conflitto handwriting Android**
- Top bar: bottone ← a sinistra (chiude tab), toolbar completa a destra
- Scroll container: `overflow-y: auto` per canvas più grandi dello schermo
- Finger scroll: `allowFingerScroll(scrollContainer)` — dito scrolla, penna disegna
- Auto-save debounced 2s + `plugin.refreshPreview()` aggiorna la preview inline
`DrawingModal extends Modal` — usato su **Windows**:
- Overlay fullscreen (`hwm_modal`: `95vw × 90vh`) che appare sopra il documento
- Stessa toolbar e stesso canvas di `DrawingEditorView`
- `onClosed?: () => void` — callback invocato alla chiusura per ripristinare `panel.style.display` nel pannello portale
- `replaceCodeBlock` / `removeCodeBlock` gestiscono sia formato wiki `![[svg]]` che legacy (prova prima wiki, poi code block come fallback)
- `private bgModeListener` registrato in `buildEditor()` (dopo `colorBtns`) e rimosso in `onClose()` — aggiorna topbar, toolbar, pallini colore e canvas al cambio bgMode in tempo reale (utile se le impostazioni fossero accessibili con modal aperto)
**Comportamento bottone matita (pannello portale):**
- `Platform.isDesktop``new DrawingModal(...).open()` — si apre nella stessa finestra
- `Platform.isMobile``workspace.getLeaf('tab')` con `DrawingEditorView`
- Bottone matita nascosto: `modalOpen || tabOpen` — scompare sia quando Modal è aperto (Windows) sia quando la tab è aperta (Android)
**Modalità handwriting (`hwmHandwritingMode`):**
- Switch nelle impostazioni: "Modalità handwriting Android"
- Se ON: `document.body.classList.add('hwm-handwriting-mode')` + classe `hwm-badge-mode` sullo span → CSS riduce l'SVG a 48px di altezza (badge/thumbnail)
- Se OFF (default): SVG piena, comportamento normale
- Si applica immediatamente senza ricaricare il plugin (toggle in settings chiama `document.body.classList.toggle(...)`; all'avvio viene applicato in `registerEmbed()`)
### Funzionalità implementate
- **Preview SVG inline** nel markdown via code block `handwriting` (immagine statica, no canvas)
- **Editor in tab dedicata** — click sulla preview apre una tab Obsidian separata
- **Curve smooth** — Bézier quadratiche con tecnica midpoint
- **Gomma parziale** — cancella solo i punti toccati, taglia i tratti in segmenti
- **Undo/Redo** — basato su history di stati (funziona sia per disegno che per gomma)
- **Auto-expand** — il canvas si espande con animazione smooth + auto-scroll nel container
- **Clear → reset** alla dimensione di default con animazione
- **Righe orizzontali** — foglio a righe (32px), sia nel canvas che nell'SVG
- **Temi sfondo** — chiaro/scuro/custom con color picker nelle impostazioni
- **Remapping colori automatico** — i tratti si adattano al cambio tema (nero↔bianco, blu↔azzurro, ecc.)
- **Toolbar completa** — penna, gomma, 4 colori, undo, redo, clear, converti, salva, elimina (X)
- **Elimina riquadro** — da inline (3 bottoni) o da tab editor
- **Auto-save** — salvataggio debounced 2s + refresh preview inline
- **SVG standard** — file `.svg` nella cartella `_handwriting/`, visibili da qualsiasi dispositivo
- **Palette colori adattiva** — colori scuri su sfondo chiaro, colori chiari su sfondo scuro
- **OCR via Gemini** — SVG → PNG base64 → Gemini 3.1 Flash Lite → `md-parser` → sostituisce code block
- **Archiviazione SVG** — dopo la conversione, SVG spostato in `_handwriting/_converted/AAAA-MM-GG_HH-MM-SS.svg`
- **Settings OCR** — chiave API Gemini (campo password) + lingue OCR configurabili (default: `it, en`)
- **Supporto Android** — penna disegna, dito scrolla, nessun conflitto handwriting
- **Comprimi/Espandi** — preview inline si può compattare all'altezza di default (freccia con rotazione 180°)
- **Bottone portale** — pannello `position: absolute` figlio diretto dello span (no RAF loop, no scroll listener, no document.body)
- **Preview non tappabile** — click handler rimosso; l'unico modo per aprire l'editor è il bottone matita nel pannello portale
- **Modal Windows** — click matita su Desktop apre `DrawingModal` (overlay fullscreen nella stessa finestra, no tab separata)
- **Nuova tab Android** — click matita su Mobile apre `DrawingEditorView` in tab separata
- **Switch handwriting** — toggle nelle Settings: se ON, i riquadri mostrano solo una piccola anteprima (48px) per non bloccare lo stylus handwriting nel testo
### Embedding nel markdown
**Nuovo formato (default):**
```markdown
![[_handwriting/hw_abc123.svg]]
```
Il file SVG è visibile come immagine anche senza il plugin. I tratti sono salvati come JSON in `<desc class="hwm-strokes">` dentro l'SVG.
**Legacy (backward compat):**
````markdown
```handwriting
{"id":"hw_abc123","svg":"_handwriting/hw_abc123.svg"}
```
````
### Deploy (comandi copia-incolla per PowerShell)
> **Nota**: usare Git Bash esplicitamente con `--login` perché PowerShell usa WSL bash di default (che non riconosce i percorsi Windows) e senza `--login` il PATH non include i tool Unix (dirname, cp, wc, ecc.).
```powershell
# Build + deploy al vault locale (solo PC)
cd C:\Projects\pluginObsidian\handWrittenMarkdownConverter\HandTranscriptMd; node esbuild.config.mjs production; & "C:\Program Files\Git\bin\bash.exe" --login deploy.sh
# Build + deploy su Google Drive (per testare su tablet Android)
cd C:\Projects\pluginObsidian\handWrittenMarkdownConverter\HandTranscriptMd; node esbuild.config.mjs production; & "C:\Program Files\Git\bin\bash.exe" --login cloudDeploy.sh
```
### Percorsi vault
- **Vault locale di test:** `C:\Projects\CLIENTI\IOTTI\IOTTI_APP\_docs\handwriting-to-markdown\`
- Plugin in `.obsidian\plugins\handwriting-to-markdown\`
- **Vault Google Drive (tablet):** `C:\Users\gabri\Il mio Drive (gabrielecusato@gmail.com)\Projects\handwriting-to-markdown\`
### Come sviluppare
```powershell
# Dev mode (watch)
cd C:\Projects\pluginObsidian\handWrittenMarkdownConverter\HandTranscriptMd; npm run dev
# Dopo ogni modifica per testare su PC:
cd C:\Projects\pluginObsidian\handWrittenMarkdownConverter\HandTranscriptMd; node esbuild.config.mjs production; bash deploy.sh
# Dopo ogni modifica per testare su tablet Android:
cd C:\Projects\pluginObsidian\handWrittenMarkdownConverter\HandTranscriptMd; node esbuild.config.mjs production; bash cloudDeploy.sh
# In Obsidian: Ctrl+P → "Reload app without saving"
```
---
## Note architetturali
### Come funziona il flusso OCR
1. `canvas.getStrokes()` → array di `Stroke[]`
2. `strokesToSvg()` → stringa SVG (già usata per il salvataggio)
3. `DOMParser``SVGElement` DOM
4. `svgToBase64Png(svgEl)` → PNG base64 via canvas HTML temporaneo (Blob URL → Image → canvas `toDataURL`)
5. `GeminiRecognizer.recognize(base64)` → POST a Gemini con `inline_data` + prompt
6. `parseMarkdown(testo)` → post-processing riga per riga
7. `archiveSvg()` → sposta SVG in `_converted/` con nome timestamp
8. `replaceEmbedWithMarkdown()` → regex sul file `.md` sostituisce il code block
### Perché Gemini e non API native Android
- `navigator.createHandwritingRecognizer` era un Origin Trial Chrome sperimentale, mai arrivato a stable
- Obsidian Mobile usa WebView → API non disponibile su Xiaomi Pad 5 né altri dispositivi
- `window.prompt()` non funziona in Electron (Obsidian desktop)
- Gemini REST API funziona identicamente su Windows e Android
### Modello Gemini usato
`gemini-3.1-flash-lite-preview` — documentazione: https://ai.google.dev/gemini-api/docs/models/gemini-3.1-flash-lite-preview
---
> **Storico sessioni, bug risolti e funzionalità completate** → vedi [`NOTES.md`](./NOTES.md)
> I task completati vanno spostati in `NOTES.md`; in questa sezione restano solo i task ancora da fare.
## Regole di sviluppo
> **Keyword parser**: ogni volta che si aggiunge o rimuove una keyword accettata in `src/md-parser.ts`, va aggiornata **anche** la tabella `KEYWORDS` in `src/settings.ts` (sezione "Keyword riconosciute dal parser OCR"). Le due liste devono essere sempre sincronizzate.
## Prossimi passi
### Task aperti
- **Bug parser `//TABLE`**`src/md-parser.ts`:
- Durante un test OCR il tag di chiusura `//TABLE` è stato riconosciuto come tag di apertura di una nuova tabella, invece di terminare quella corrente
- I valori delle celle non sono stati inseriti nella tabella
- Verificare la logica di parsing delle righe successive al tag `//TABLE`: probabilmente una riga contenente solo `//TABLE` (o simile) viene re-interpretata come nuovo comando invece di essere ignorata/consumata
- **Keyword personalizzate nelle impostazioni**`src/settings.ts` + `src/md-parser.ts`:
- Permettere all'utente di aggiungere keyword custom nella pagina impostazioni
- Ogni keyword custom ha: nome (es. `FIRMA`), output markdown (es. `— Mario Rossi`)
- Le keyword custom vengono caricate in `expandKeywords` insieme a quelle predefinite

View file

@ -1,251 +0,0 @@
# Obsidian community plugin
## Project overview
- Target: Obsidian Community Plugin (TypeScript → bundled JavaScript).
- Entry point: `main.ts` compiled to `main.js` and loaded by Obsidian.
- Required release artifacts: `main.js`, `manifest.json`, and optional `styles.css`.
## Environment & tooling
- Node.js: use current LTS (Node 18+ recommended).
- **Package manager: npm** (required for this sample - `package.json` defines npm scripts and dependencies).
- **Bundler: esbuild** (required for this sample - `esbuild.config.mjs` and build scripts depend on it). Alternative bundlers like Rollup or webpack are acceptable for other projects if they bundle all external dependencies into `main.js`.
- Types: `obsidian` type definitions.
**Note**: This sample project has specific technical dependencies on npm and esbuild. If you're creating a plugin from scratch, you can choose different tools, but you'll need to replace the build configuration accordingly.
### Install
```bash
npm install
```
### Dev (watch)
```bash
npm run dev
```
### Production build
```bash
npm run build
```
## Linting
- To use eslint install eslint from terminal: `npm install -g eslint`
- To use eslint to analyze this project use this command: `eslint main.ts`
- eslint will then create a report with suggestions for code improvement by file and line number.
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder: `eslint ./src/`
## File & folder conventions
- **Organize code into multiple files**: Split functionality across separate modules rather than putting everything in `main.ts`.
- Source lives in `src/`. Keep `main.ts` small and focused on plugin lifecycle (loading, unloading, registering commands).
- **Example file structure**:
```
src/
main.ts # Plugin entry point, lifecycle management
settings.ts # Settings interface and defaults
commands/ # Command implementations
command1.ts
command2.ts
ui/ # UI components, modals, views
modal.ts
view.ts
utils/ # Utility functions, helpers
helpers.ts
constants.ts
types.ts # TypeScript interfaces and types
```
- **Do not commit build artifacts**: Never commit `node_modules/`, `main.js`, or other generated files to version control.
- Keep the plugin small. Avoid large dependencies. Prefer browser-compatible packages.
- Generated output should be placed at the plugin root or `dist/` depending on your build setup. Release artifacts must end up at the top level of the plugin folder in the vault (`main.js`, `manifest.json`, `styles.css`).
## Manifest rules (`manifest.json`)
- Must include (non-exhaustive):
- `id` (plugin ID; for local dev it should match the folder name)
- `name`
- `version` (Semantic Versioning `x.y.z`)
- `minAppVersion`
- `description`
- `isDesktopOnly` (boolean)
- Optional: `author`, `authorUrl`, `fundingUrl` (string or map)
- Never change `id` after release. Treat it as stable API.
- Keep `minAppVersion` accurate when using newer APIs.
- Canonical requirements are coded here: https://github.com/obsidianmd/obsidian-releases/blob/master/.github/workflows/validate-plugin-entry.yml
## Testing
- Manual install for testing: copy `main.js`, `manifest.json`, `styles.css` (if any) to:
```
<Vault>/.obsidian/plugins/<plugin-id>/
```
- Reload Obsidian and enable the plugin in **Settings → Community plugins**.
## Commands & settings
- Any user-facing commands should be added via `this.addCommand(...)`.
- If the plugin has configuration, provide a settings tab and sensible defaults.
- Persist settings using `this.loadData()` / `this.saveData()`.
- Use stable command IDs; avoid renaming once released.
## Versioning & releases
- Bump `version` in `manifest.json` (SemVer) and update `versions.json` to map plugin version → minimum app version.
- Create a GitHub release whose tag exactly matches `manifest.json`'s `version`. Do not use a leading `v`.
- Attach `manifest.json`, `main.js`, and `styles.css` (if present) to the release as individual assets.
- After the initial release, follow the process to add/update your plugin in the community catalog as required.
## Security, privacy, and compliance
Follow Obsidian's **Developer Policies** and **Plugin Guidelines**. In particular:
- Default to local/offline operation. Only make network requests when essential to the feature.
- No hidden telemetry. If you collect optional analytics or call third-party services, require explicit opt-in and document clearly in `README.md` and in settings.
- Never execute remote code, fetch and eval scripts, or auto-update plugin code outside of normal releases.
- Minimize scope: read/write only what's necessary inside the vault. Do not access files outside the vault.
- Clearly disclose any external services used, data sent, and risks.
- Respect user privacy. Do not collect vault contents, filenames, or personal information unless absolutely necessary and explicitly consented.
- Avoid deceptive patterns, ads, or spammy notifications.
- Register and clean up all DOM, app, and interval listeners using the provided `register*` helpers so the plugin unloads safely.
## UX & copy guidelines (for UI text, commands, settings)
- Prefer sentence case for headings, buttons, and titles.
- Use clear, action-oriented imperatives in step-by-step copy.
- Use **bold** to indicate literal UI labels. Prefer "select" for interactions.
- Use arrow notation for navigation: **Settings → Community plugins**.
- Keep in-app strings short, consistent, and free of jargon.
## Performance
- Keep startup light. Defer heavy work until needed.
- Avoid long-running tasks during `onload`; use lazy initialization.
- Batch disk access and avoid excessive vault scans.
- Debounce/throttle expensive operations in response to file system events.
## Coding conventions
- TypeScript with `"strict": true` preferred.
- **Keep `main.ts` minimal**: Focus only on plugin lifecycle (onload, onunload, addCommand calls). Delegate all feature logic to separate modules.
- **Split large files**: If any file exceeds ~200-300 lines, consider breaking it into smaller, focused modules.
- **Use clear module boundaries**: Each file should have a single, well-defined responsibility.
- Bundle everything into `main.js` (no unbundled runtime deps).
- Avoid Node/Electron APIs if you want mobile compatibility; set `isDesktopOnly` accordingly.
- Prefer `async/await` over promise chains; handle errors gracefully.
## Mobile
- Where feasible, test on iOS and Android.
- Don't assume desktop-only behavior unless `isDesktopOnly` is `true`.
- Avoid large in-memory structures; be mindful of memory and storage constraints.
## Agent do/don't
**Do**
- Add commands with stable IDs (don't rename once released).
- Provide defaults and validation in settings.
- Write idempotent code paths so reload/unload doesn't leak listeners or intervals.
- Use `this.register*` helpers for everything that needs cleanup.
**Don't**
- Introduce network calls without an obvious user-facing reason and documentation.
- Ship features that require cloud services without clear disclosure and explicit opt-in.
- Store or transmit vault contents unless essential and consented.
## Common tasks
### Organize code across multiple files
**main.ts** (minimal, lifecycle only):
```ts
import { Plugin } from "obsidian";
import { MySettings, DEFAULT_SETTINGS } from "./settings";
import { registerCommands } from "./commands";
export default class MyPlugin extends Plugin {
settings: MySettings;
async onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
registerCommands(this);
}
}
```
**settings.ts**:
```ts
export interface MySettings {
enabled: boolean;
apiKey: string;
}
export const DEFAULT_SETTINGS: MySettings = {
enabled: true,
apiKey: "",
};
```
**commands/index.ts**:
```ts
import { Plugin } from "obsidian";
import { doSomething } from "./my-command";
export function registerCommands(plugin: Plugin) {
plugin.addCommand({
id: "do-something",
name: "Do something",
callback: () => doSomething(plugin),
});
}
```
### Add a command
```ts
this.addCommand({
id: "your-command-id",
name: "Do the thing",
callback: () => this.doTheThing(),
});
```
### Persist settings
```ts
interface MySettings { enabled: boolean }
const DEFAULT_SETTINGS: MySettings = { enabled: true };
async onload() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
await this.saveData(this.settings);
}
```
### Register listeners safely
```ts
this.registerEvent(this.app.workspace.on("file-open", f => { /* ... */ }));
this.registerDomEvent(window, "resize", () => { /* ... */ });
this.registerInterval(window.setInterval(() => { /* ... */ }, 1000));
```
## Troubleshooting
- Plugin doesn't load after build: ensure `main.js` and `manifest.json` are at the top level of the plugin folder under `<Vault>/.obsidian/plugins/<plugin-id>/`.
- Build issues: if `main.js` is missing, run `npm run build` or `npm run dev` to compile your TypeScript source code.
- Commands not appearing: verify `addCommand` runs after `onload` and IDs are unique.
- Settings not persisting: ensure `loadData`/`saveData` are awaited and you re-render the UI after changes.
- Mobile-only issues: confirm you're not using desktop-only APIs; check `isDesktopOnly` and adjust.
## References
- Obsidian sample plugin: https://github.com/obsidianmd/obsidian-sample-plugin
- API documentation: https://docs.obsidian.md
- Developer policies: https://docs.obsidian.md/Developer+policies
- Plugin guidelines: https://docs.obsidian.md/Plugins/Releasing/Plugin+guidelines
- Style guide: https://help.obsidian.md/style-guide

View file

@ -1,20 +0,0 @@
#!/bin/bash
# Deploy plugin files to Google Drive vault (sync cloud)
# Uso: bash cloudDeploy.sh
# MSYS_NO_PATHCONV=1 impedisce a Git Bash di convertire i path C:/ in /c/
# ed evita che mkdir crei una cartella "C:" relativa
export MSYS_NO_PATHCONV=1
VAULT_PLUGIN="C:/Users/gabri/Il mio Drive (gabrielecusato@gmail.com)/Projects/handwriting-to-markdown"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
mkdir -p "$VAULT_PLUGIN"
cp "$SCRIPT_DIR/main.js" "$SCRIPT_DIR/manifest.json" "$SCRIPT_DIR/styles.css" "$VAULT_PLUGIN/"
echo "Deployed to $VAULT_PLUGIN"
echo " main.js $(wc -c < "$VAULT_PLUGIN/main.js") bytes"
echo " manifest.json"
echo " styles.css"
echo ""
echo "In Obsidian: Ctrl+P -> 'Reload app without saving'"

View file

@ -1,18 +0,0 @@
#!/bin/bash
# Deploy plugin files to Obsidian vault
# Uso: bash deploy.sh
export MSYS_NO_PATHCONV=1
VAULT_PLUGIN="C:/Projects/CLIENTI/IOTTI/IOTTI_APP/_docs/handwriting-to-markdown/.obsidian/plugins/handwriting-to-markdown"
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
mkdir -p "$VAULT_PLUGIN"
cp "$SCRIPT_DIR/main.js" "$SCRIPT_DIR/manifest.json" "$SCRIPT_DIR/styles.css" "$VAULT_PLUGIN/"
echo "Deployed to $VAULT_PLUGIN"
echo " main.js $(wc -c < "$VAULT_PLUGIN/main.js") bytes"
echo " manifest.json"
echo " styles.css"
echo ""
echo "In Obsidian: Ctrl+P -> 'Reload app without saving'"

375
NOTES.md
View file

@ -1,375 +0,0 @@
# Note storiche — Handwriting Plugin
Questo file contiene lo storico delle sessioni di sviluppo, bug risolti, tentativi falliti e ricerche effettuate.
Per istruzioni, architettura e task aperti vedi `CLAUDE.md`.
---
## ✅ Task completati (sessione 2026-03-24 — parte 2)
### Canvas a tutto schermo nell'overlay (DPR + viewScale + fix portrait) — IMPLEMENTATO ✅
**Obiettivo**: eliminare le bande laterali vuote nell'overlay di disegno (modal Windows e tab Android), senza pixelazione e con supporto alla rotazione portrait/landscape.
**Architettura introdotta** (`src/drawing-canvas.ts`):
- `dpr` — device pixel ratio; il buffer interno del canvas è `logicalWidth * dpr` fisici, ma il contesto (`ctx.scale(dpr, dpr)`) lavora sempre in pixel logici → nessuna pixelazione su display Retina.
- `logicalWidth/Height` — dimensione CSS effettiva del canvas; aggiornata ad ogni cambio di orientamento.
- `worldWidth` — spazio coordinate dei tratti salvati nel SVG. **Non scende mai**: se l'utente disegna in landscape (1280px) e poi passa a portrait (720px), `worldWidth` rimane 1280 così il SVG non perde tratti.
- `viewScale = logicalWidth / worldWidth` — fattore di scala orizzontale. In portrait (720/1280 ≈ 0.56) tutto il contenuto viene compresso per mostrarlo senza tagliare. `eventToPoint()` divide la coordinata CSS per `viewScale` per tornare alle coordinate mondo; `drawFullStroke/drawSegment` applicano `ctx.scale(viewScale, 1.0)` prima di disegnare.
- `setDisplayWidth(w)` — chiamato dal `ResizeObserver` ad ogni cambio orientamento: se `w > worldWidth` espande il mondo; se `w < worldWidth` aggiorna solo `logicalWidth` e `viewScale`.
- Ogni modifica di `canvas.width/height` resetta il contesto → `ctx.scale(dpr, dpr)` viene ri-applicato subito dopo.
**Windows — `DrawingModal`** (`src/editor-view.ts`): un `requestAnimationFrame` dopo la costruzione misura `scrollWrap.clientWidth` e chiama `setDisplayWidth`.
**Android — `DrawingEditorView`** (`src/editor-view.ts`): un `ResizeObserver` su `scrollWrap` e `el` chiama `setDisplayWidth` ogni volta che il layout cambia (inclusa rotazione). Il riferimento è salvato in `this.displayRo` e disconnesso in `onClose()`.
**Bug fix — SVG tagliato alla riapertura in portrait** (`src/editor-view.ts`): entrambe le `loadStrokes()` (in `DrawingEditorView` e `DrawingModal`) leggono ora **anche la larghezza** dal `viewBox` dell'SVG (`viewBox="0 0 W H"`). Il canvas viene creato con `worldWidth = savedW ?? settings.canvasWidth`, così riaprendolo in portrait non si perde il `worldWidth` della sessione precedente.
**File**: `src/drawing-canvas.ts`, `src/editor-view.ts`.
---
## ✅ Task completati (sessione 2026-03-24)
### Fix focus perso dopo `window.confirm()` — RISOLTO ✅
**Sintomo**: dopo aver confermato o annullato la cancellazione di un SVG (sia dal pannello portale inline che dalla `DrawingModal`), il focus veniva perso e non era più possibile scrivere nel documento Obsidian finché non si cambiava finestra.
**Causa**: `window.confirm()` in Electron apre un dialogo nativo che rimuove il focus a livello OS dalla finestra Electron. Alla chiusura il focus non viene ripristinato automaticamente.
**Fix — `DrawingModal`** (`src/editor-view.ts`): aggiunto metodo `showDeleteConfirm()` che crea un overlay `<div class="hwm_confirm-overlay">` dentro `contentEl` con due bottoni ("Elimina" e "Annulla"). Nessun dialogo nativo, nessun problema di focus. La `doDelete()` del modal usa ora `showDeleteConfirm()` + listener `vault.on('modify')` + `setTimeout(300)` per ripristinare il focus dopo la cancellazione del file.
**Fix — pannello portale inline** (`src/embed.ts`): aggiunta funzione helper `showInlineConfirm(anchorEl, msg)` che crea lo stesso overlay `position:absolute` sopra l'elemento passato. Sia il bottone elimina del formato wiki che quello del formato legacy usano ora `showInlineConfirm`.
**File**: `src/editor-view.ts`, `src/embed.ts`, `styles.css`, `src/locales/*.json` (chiavi `confirm_ok`, `confirm_cancel`).
### Fix overlay di conferma non cliccabile nel pannello portale — RISOLTO ✅
**Sintomo**: dopo la conversione all'overlay inline, i bottoni "Elimina" e "Annulla" non erano cliccabili nel pannello portale. Era anche possibile cliccare i bottoni sottostanti attraverso l'overlay.
**Causa**: due problemi CSS simultanei — (1) l'overlay aveva `z-index: 10`, uguale al pannello portale; (2) il container span eredita `pointer-events: none` e l'overlay lo ereditava.
**Fix CSS** (`styles.css`): `z-index: 100` sull'overlay + `pointer-events: auto !important` su `.hwm_confirm-overlay` e sui suoi bottoni.
**File**: `styles.css`.
### Fix letterboxing (bordi neri) su Android al comprimi/espandi — RISOLTO ✅
**Sintomo**: su Android, comprimere o espandere un riquadro SVG creava grossi bordi neri ai lati dell'immagine invece di ridimensionarla correttamente.
**Causa**: Obsidian Mobile ha un `ResizeObserver` interno sul container span. Quando `container.style.height` veniva modificato, il ResizeObserver si attivava e ricalcolava il layout dell'`<img>`, forzando proporzioni con letterbox.
**Fix**: tecnica del "wrapper div" — `doCollapse`/`doExpand` in `embed.ts` creano (o riusano) un `<div class="hwm_clip-wrapper">` figlio diretto dell'`<img>`. L'animazione viene applicata sull'altezza del wrapper, mai sul container span, quindi il ResizeObserver non si attiva. `img.parentElement.insertBefore(wrapper, img)` per evitare eccezione silenziosa su Android dove `<img>` non è figlio diretto del container.
**CSS aggiunto** (`.hwm_clip-wrapper`): `width: 100%; transition: height 0.3s ease;` e regole sull'img figlio per forzare `width: 100%; height: auto; object-fit: unset`.
**File**: `src/embed.ts`, `styles.css`.
### Animazione comprimi/espandi — AGGIUNTA ✅
**Cosa**: l'altezza del wrapper si anima con `transition: height 0.3s ease`. Al collapse: `scrollHeight``collapsedHeight` (px) via `requestAnimationFrame`. All'expand: `collapsedHeight``scrollHeight`, poi `height: ''` e `overflow: ''` rimossi su `transitionend` per ripristinare il layout naturale.
**File**: `src/embed.ts`, `styles.css`.
### Rimozione nome branch dalla versione nelle impostazioni — RISOLTO ✅
**Cosa**: il header della pagina impostazioni mostrava `v1.x.x — branch: overlay`. La stringa del branch è stata rimossa; ora mostra solo `v${this.plugin.manifest.version}`.
**File**: `src/settings.ts` (rimossa costante `PLUGIN_BRANCH` dall'UI, variabile mantenuta per uso interno).
### Preview SVG nella ricerca "Insert SVG reference" — AGGIUNTA ✅
**Cosa**: il modal fuzzy-search per inserire un riferimento a un SVG esistente ora mostra una thumbnail dell'SVG a sinistra del nome file, invece del solo testo.
**Implementazione**: override di `renderSuggestion()` in `SvgReferenceSuggest` (`src/main.ts`). Usa `app.vault.getResourcePath(file)` come `src` dell'`<img>`. CSS aggiunto: `.hwm_svg-suggest-item` (flex), `.hwm_svg-thumb` (48×48px, border-radius), `.hwm_svg-suggest-name`.
**File**: `src/main.ts`, `styles.css`.
---
## ✅ Task completati (sessione 2026-03-23)
### Tema automatico (bgMode 'auto')
Rinominato 'custom' → 'auto' nel dropdown settings. `MutationObserver` su `document.body` in `main.ts` chiama `notifyBgModeChange()` al cambio di `theme-dark`. Aggiunta `resolveIsDark(bgMode)` in `editor-view.ts` ed `embed.ts` per risolvere 'auto' al tema Obsidian effettivo. Migrazione automatica 'custom'→'auto' in `loadSettings()`. Rimosso `bgCustomColor` e color picker dalle settings.
### Angoli arrotondati
Usato `var(--radius-m)` / `var(--radius-l)` su tutti gli elementi (toolbar, bottoni, pannello portale, modal, container). `border-radius` applicato sull'`<img>` inline invece che sullo span (no `overflow:hidden` sullo span, che clippava l'SVG). Aggiunto `border: 1.5px solid var(--background-modifier-border)` e `box-shadow` al riquadro inline per visibilità contro lo sfondo Obsidian.
### Rimosso switch "Modalità handwriting Android"
Badge mode sempre attiva su `Platform.isMobile`, preview piena sempre su desktop. Rimossi `hwmHandwritingMode` da `HandwritingSettings`, `DEFAULT_SETTINGS`, settings tab, `registerEmbed()` e `tryDecorate()` in `embed.ts`. Le classi CSS `hwm-handwriting-mode` e `hwm-badge-mode` rimangono ma vengono applicate automaticamente.
### Fix toolbar pannello portale al cambio tema
Sostituita classe generica `hwm_toolbar--dark` con `hwm_portal-panel--dark` dedicata. `resolveIsDark` inline in `createPortalPanel` gestisce 'auto', 'light' e 'dark'. `hwm_resize-handle--dark` sostituisce gli inline styles sull'handle del canvas.
---
---
## ✅ TEST EFFETTUATI (sessione 2026-03-22)
### Bug 1 — Pannello portale parzialmente visibile con Modal aperto — RISOLTO ✅
**Sintomo**: su Windows, cliccando il bottone matita, i bottoni "Converti" e "Comprimi" del pannello portale restavano visibili sopra il modal mentre il bottone matita scompariva correttamente.
**Causa**: il RAF loop nascondeva solo il bottone matita (`btn.style.display`) ma non l'intero pannello.
**Fix**: il RAF loop ora setta `panel.style.display = 'none'` quando `modalOpen || tabOpen`, nascondendo l'intero pannello portale (tutti e 4 i bottoni).
**File**: `src/embed.ts` — RAF loop in `decorateSpan()`.
---
### Bug 2 — Canvas modal troppo largo, toolbar non centrata — RISOLTO ✅
**Sintomo**: nel Modal Windows, il canvas occupava tutta la larghezza dell'overlay (troppo largo e non centrato). La toolbar era allineata a sinistra invece che al centro.
**Fix CSS** (`styles.css`):
- `.hwm_canvas-wrap { display: flex; justify-content: center; }` — centra il canvas orizzontalmente
- `.hwm_canvas { max-width: 100%; }` — rimosso `width: 100%` fisso
- `.hwm_editor-topbar--modal { justify-content: center; }` — centra la toolbar nel modal
**Fix TS** (`editor-view.ts`): `DrawingModal.buildEditor()` aggiunge classe `hwm_editor-topbar--modal` alla topbar.
---
### Bug 3 — Auto-scroll sposta i tratti durante il disegno — RISOLTO ✅
**Sintomo**: quando il canvas si espandeva automaticamente (auto-expand) mentre stavo disegnando, lo scroll automatico verso il basso spostava i punti del tratto corrente rispetto alla posizione del pennino.
**Causa**: l'evento `onResize` faceva scroll immediatamente anche con il pointer premuto, spostando il canvas mentre le coordinate del puntatore erano ancora relative alla posizione pre-scroll.
**Fix**: aggiunto metodo pubblico `isPointerDown(): boolean` in `DrawingCanvas` (`drawing-canvas.ts`). Sia `DrawingEditorView` che `DrawingModal` in `editor-view.ts` ora controllano `!canvas.isPointerDown()` prima di eseguire il `scrollTop` automatico.
---
### Bug 4 — Badge mode mostra icona in riquadro piccolissimo — RISOLTO ✅
**Sintomo**: attivando "Modalità handwriting Android" nelle impostazioni, i riquadri SVG si riducevano a un quadratino minuscolo invece di un badge orizzontale a piena larghezza.
**Causa**: lo span `.internal-embed` senza figli visibili collassava alla sua larghezza intrinseca (quasi zero).
**Fix CSS** (`.hwm-handwriting-mode .hwm-badge-mode`):
```css
width: 100% !important; box-sizing: border-box !important;
height: 72px !important; display: flex !important;
align-items: center; justify-content: center;
background: var(--background-secondary); border-radius: 6px;
```
Più: `img { display: none !important }` per nascondere l'SVG e `::after { content: "✏️"; font-size: 28px; opacity: 0.5; }` per l'icona.
**File**: `styles.css`.
---
## ✅ TEST EFFETTUATI (sessione 2026-03-23)
### Bug 5 — Pannello portale visibile nelle impostazioni e ovunque — RISOLTO ✅
**Sintomo**: il pannello portale (`position: fixed` in `document.body`) rimaneva visibile in alto a destra anche navigando nelle impostazioni, in altre schede, ecc.
**Causa**: il pannello era appeso a `document.body` e il RAF loop di posizionamento lo seguiva solo quando lo span era nel viewport.
**Fix**: pannello spostato come figlio diretto dello span contenitore con `position: absolute; top: 6px; right: 6px`. Lo span ha `position: relative`. Il pannello è ora parte del DOM del documento e scompare naturalmente quando si naviga altrove.
**File**: `src/embed.ts``createPortalPanel()` + `styles.css`.
---
### Bug 6 — Comprimi/Espandi modificava anche la larghezza — RISOLTO ✅
**Sintomo**: cliccando il bottone freccia per comprimere/espandere il riquadro, anche la larghezza cambiava (l'immagine si restringeva).
**Causa**: il codice precedente usava `max-height` sull'elemento `<img>`, che lo scalava proporzionalmente.
**Fix**: la compressione ora modifica `container.style.height` + `overflow: hidden` sullo span contenitore. L'immagine viene clippata verticalmente senza alterarne la larghezza.
**File**: `src/embed.ts` — handler del bottone collapse in `createPortalPanel()`.
---
### Bug 7 — Pannello portale rimane nel DOM dopo disabilitazione plugin — RISOLTO ✅
**Sintomo**: disabilitando il plugin, i pannelli portale (bottoni) rimanevano visibili nel documento.
**Fix**: `plugin.register(() => panel.remove())` — Obsidian chiama tutti i callback registrati con `plugin.register()` quando il plugin viene disabilitato.
**File**: `src/embed.ts``createPortalPanel()`.
---
### Bug 8 — Bottoni pannello portale non cliccabili — RISOLTO ✅
**Sintomo**: dopo lo spostamento del pannello dentro lo span, i bottoni non rispondevano al click.
**Causa**: lo span aveva `pointer-events: none` (impostato in `tryDecorate()` per handwriting Android). Il pannello figlio ereditava la proprietà.
**Fix**: `pointer-events: auto` in CSS su `.hwm_portal-panel` — ripristina i click solo sul pannello, lasciando il resto dello span non interattivo.
**File**: `styles.css`.
---
### Bug 9 — Palette colori SVG non aggiornata al cambio bgMode — RISOLTO ✅
**Sintomo**: cambiando bgMode nelle impostazioni, gli SVG nei documenti aperti mantenevano i vecchi colori dei tratti finché non si ricaricava il plugin.
**Fix**: aggiunto listener `onBgModeRemap` in `registerEmbed()` registrato in `plugin.bgModeListeners`. Quando il bgMode cambia:
1. Itera `plugin.embedPaths` (mappa `embedId → svgPath`)
2. Legge il file SVG dal vault e verifica il marker `hwm-strokes` (ignora SVG non del plugin)
3. Parsa `viewBox="0 0 W H"` per ottenere le dimensioni reali (non quelle di default, che perderebbero l'auto-expand)
4. Rimappa i colori dei tratti con `remapStrokeColor()`, rigenera l'SVG con `strokesToSvg()`, salva
5. Chiama `plugin.refreshPreview(embedId, newContent)` → aggiorna `img.src` con cache-bust
**File**: `src/embed.ts``onBgModeRemap` in `registerEmbed()`.
---
### Bug 10 — Toolbar editor non aggiornata al cambio bgMode — RISOLTO ✅
**Sintomo**: i bottoni della toolbar nel `DrawingModal` (Windows) e nel `DrawingEditorView` (Android) non cambiavano colore al cambio bgMode.
**Causa 1 (live update)**: mancava un listener. Aggiunto `bgModeListener` in entrambe le classi, registrato in `bgModeListeners` dopo la costruzione di `colorBtns` (per poterli aggiornare nel closure). Rimosso in `onClose()`.
**Causa 2 (riapertura)**: il cambio bgMode avviene sempre con editor chiuso. `buildEditor()` viene chiamato di nuovo alla riapertura e legge `plugin.settings.bgMode` → corretto per costruzione. Il problema visivo era CSS.
**Causa 3 (CSS)**: Obsidian dark theme ha regole tipo `.modal-content button { background: var(...) }` con specificità `0,1,1` > `0,1,0` di `.hwm_btn`, sovrascrivendo il nostro sfondo trasparente. Il colore del topbar senza `!important` veniva sovrascritto analogamente.
**Fix CSS** (`styles.css`):
- `.hwm_editor-topbar { background: rgba(240,240,240,0.95) !important }` e `.hwm_editor-topbar--dark { background: rgba(40,40,40,0.97) !important }` — entrambe con `!important` (la `--dark` appare dopo → vince in dark mode per cascade order)
- `.hwm_editor-topbar .hwm_btn { background: transparent !important; color: #333 !important }` — batte la specificità di Obsidian
- `.hwm_editor-topbar--dark .hwm_btn { color: #bbb !important }` — appare dopo → vince in dark mode
- Hover e active espliciti con `!important` per entrambe le modalità
**Nota importante**: il cambio bgMode viene SEMPRE effettuato con editor chiuso (il modal copre l'intera finestra). Il listener live è presente ma non è il percorso principale.
**File**: `src/editor-view.ts` + `styles.css`.
---
## Problemi risolti (storico completo)
- **Handwriting Android (disegno)** ✅ — risolto con editor in tab separata (`ItemView`), canvas fuori da `cm-content`
- **Pen scroll** ✅ — penna non scrolla più, solo dito (JS manuale via `setPointerCapture`)
- **Toolbar — tema scuro**
- **Spazio vuoto sezione colori in toolbar compatta**
- **Trashcan non cancella visualmente**
- **Bottoni inline coprivano `</>` di Obsidian** ✅ — spostati a `left: 6px`
- **Ordine bottoni inline** ✅ — invertito: X, Converti, Freccia (da sinistra)
- **Placeholder text** ✅ — aggiornato a "Usa il bottone matita in alto a destra per disegnare"
- **Bottone portale non cerchio perfetto** ✅ — risolto con `width/height/min-width/min-height: 36px !important`, `padding: 0 !important`, `overflow: hidden`
- **Icona bottone portale non visibile** ✅ — SVG con `stroke="currentColor"` non diventava bianco; risolto con `.hwm_portal-btn svg { stroke: #ffffff !important }`
- **Bottone portale non si nasconde con editor aperto** ✅ — check `workspace.getLeavesOfType(VIEW_TYPE_HANDWRITING).some(...)` nel RAF loop (Android) + flag `modalOpen` (Windows)
- **Bottone portale `position: absolute` invece di `fixed`** ✅ — `getBoundingClientRect()` restituisce coordinate viewport, non serviva aggiungere `scrollY/scrollX`
- **Bottoni pannello portale rimangono visibili durante lo scroll** ✅ — listener `scroll` su `.cm-scroller`/`.markdown-reading-view` che setta `visibility: hidden` durante lo scroll
- **Modal Windows** ✅ — implementato `DrawingModal extends Modal`; click matita su Desktop apre modal invece di nuova tab
- **Switch handwriting** ✅ — `hwmHandwritingMode` in settings; badge mode via classe CSS su `document.body` e sullo span
- **DrawingModal non gestiva formato wiki** ✅ — aggiunto `wikiEmbedRegex()` e logica try-wiki-then-legacy in `replaceInMd()`
- **Pannello portale visibile con Modal aperto (Bug 1)** ✅ — RAF loop ora nasconde l'intero panel (`display: none`) quando `modalOpen || tabOpen`, non solo il bottone matita
- **Canvas modal non centrato, toolbar a sinistra (Bug 2)** ✅ — `display: flex; justify-content: center` su `.hwm_canvas-wrap`; `max-width: 100%` su `.hwm_canvas`; classe `hwm_editor-topbar--modal` aggiunta al topbar del modal per centrare la toolbar
- **Auto-scroll sposta tratti durante disegno (Bug 3)** ✅ — aggiunto `isPointerDown()` in `DrawingCanvas`; scroll automatico bloccato se il pointer è premuto, sia in `DrawingEditorView` che in `DrawingModal`
- **Badge mode mostra riquadro minuscolo (Bug 4)** ✅ — CSS `.hwm-handwriting-mode .hwm-badge-mode` con `width: 100% !important`, `height: 72px`, flex centrato, `img { display: none }` + `::after` con emoji matita
- **Pannello portale visibile nelle impostazioni (Bug 5)** ✅ — pannello spostato da `document.body` (position:fixed) a figlio diretto dello span (position:absolute); eliminati RAF loop e scroll listener
- **Comprimi/Espandi modificava larghezza (Bug 6)** ✅ — usa `container.style.height + overflow:hidden` invece di `max-height` sull'img
- **Pannello resta dopo disabilitazione plugin (Bug 7)** ✅ — `plugin.register(() => panel.remove())`
- **Bottoni pannello non cliccabili (Bug 8)** ✅ — `pointer-events: auto` su `.hwm_portal-panel` in CSS
- **SVG non aggiornati al cambio bgMode (Bug 9)** ✅ — listener `onBgModeRemap` in `bgModeListeners`; legge viewBox per dimensioni reali, rimappa colori, salva SVG, refresh preview
- **Toolbar editor non aggiornata al cambio bgMode (Bug 10)** ✅ — `bgModeListener` in `DrawingEditorView` e `DrawingModal`; CSS `!important` su topbar e bottoni per battere specificità Obsidian dark theme
---
## BUG APERTO — Handwriting disabilitato nel documento quando il riquadro è presente
**Sintomo**: quando nel documento è presente un riquadro handwriting con un disegno (SVG non vuoto), la stylus handwriting-to-text di Android smette di funzionare nell'intero editor. Cancellare il riquadro ripristina l'handwriting. Il problema persiste tra riavvii di Obsidian.
**Progressione delle scoperte**:
**Fase 1 — Formato code block** (tentativi 16-26):
- `contenteditable="false"` su wrapper CM6 → rimosso → non risolve
- `touch-action: none` → rimosso → non risolve
- `background-image` SVG → rimossa → non risolve
- `<canvas>` nel DOM → rimosso → non risolve
- Canvas in `document.body` (fuori da CM6) toccato con stylus → **rompe handwriting** — conclusione: è il canvas element quando toccato dalla stylus, non la sua posizione nel DOM
- **Causa root fase 1**: Android WebView tratta qualsiasi `<canvas>` toccato dalla stylus come "drawing surface" e disabilita handwriting-to-text a livello di sessione WebView
**Fase 2 — Passaggio a formato wiki `![[svg]]`** (sessione 2026-03-19):
L'obiettivo era eliminare il `<canvas>` dal documento e mostrare solo l'`<img>` nativa di Obsidian.
Problema riscontrato: l'SVG vuoto (300px di altezza) NON rompe l'handwriting. L'SVG con un disegno (altezza variabile dopo auto-expand) SÌ lo rompe — anche dopo riavvio Obsidian, anche senza mai aprire la tab editor.
Cambiamenti implementati durante la fase 2:
- Passaggio da code block a `![[svg]]` come formato principale
- `insertHandwritingBlock()` crea il file SVG PRIMA di inserire il wikilink (altrimenti Obsidian mostra "could not be found")
- MutationObserver su `document.body` per intercettare gli span (il post-processor non funziona per i widget CM6 immagine in live preview)
- Fix data-URI: `img.src = data:image/svg+xml,...` → cambiato in cache-bust URL (`?t=timestamp`) perché la data-URI veniva interpretata da Android come drawing surface
- Rimosso `addWikiOverlay` (aggiungeva `hwm_inline-buttons` come figlio dello span): la struttura dello span è ora identica a un'immagine normale
- Pannello portale (`hwm_portal-panel`) in `document.body` con tutti e 4 i bottoni
**Fase 3 — Test approfonditi (sessione 2026-03-22)**:
**Test A — Rimozione `touch-action: none` da `.hwm_resize-handle`** (tentativo 29):
- Motivazione: `touch-action: none` è uno dei segnali che Android WebView usa per identificare "drawing surfaces". Rimuovendolo dal resize handle, si riduce il numero di elementi che si qualificano come drawing surface.
- Risultato: **non risolve**. L'handwriting si rompe ugualmente dopo aver aperto la tab editor.
**Test B — IME reset alla chiusura della tab editor** (tentativo 30):
- Motivazione: ipotesi che la sessione IME (Input Method Engine) di Android venisse "bloccata" dall'apertura della DrawingEditorView. Un blur/focus sul `cm-content` dopo la chiusura avrebbe potuto resettarla.
- Implementazione: `DrawingEditorView.onClose()` fa `cm.blur(); setTimeout(() => cm.focus(), 80)` solo su mobile.
- Risultato: **non risolve**. L'handwriting rimane rotto dopo la chiusura.
**Test C — Sostituzione `<canvas>` con `<svg>` nel motore di disegno** (tentativo 31):
- Motivazione (Opzione C dalle ipotesi): il `<canvas>` è l'elemento che Android WebView riconosce come drawing surface. Sostituendolo con un `<svg>` (che disegna tramite elementi `<path>`), il motore di disegno non avrebbe più alcun canvas DOM.
- Implementazione: riscrittura completa di `drawing-canvas.ts` con `SVGElement`, `<rect>` per sfondo, `<g>` per righe e tratti, `<path>` per ogni tratto con Bézier midpoint. Rimosso `touch-action: none`.
- Risultato: **non risolve**. L'handwriting si rompe ugualmente dopo aver aperto la tab editor SVG. Implementazione reverted dall'utente.
**Test D — Apertura tab senza disegnare nulla** (tentativo 32):
- Motivazione: verificare se il trigger fosse il canvas toccato dalla stylus oppure la semplice apertura della tab.
- Test: aprire la DrawingEditorView, non toccarla, chiuderla → tentare handwriting nel documento.
- Risultato: **handwriting rotto anche senza aver toccato nulla nel canvas**. Il trigger è l'apertura della tab, non il disegno. Questo esclude che `touch-action`, `setPointerCapture` o qualsiasi evento di disegno siano la causa.
**Test E — Apertura in nuova finestra Obsidian** (tentativo 33):
- Motivazione: Obsidian Mobile già apre la tab editor in una nuova finestra separata (`workspace.getLeaf('tab')`). Ipotesi che la separazione di finestra potesse isolare lo stato IME.
- Risultato: **non risolve**. Confermato dall'utente che l'editor già apre in una finestra separata, ma il problema persiste. Tutto il runtime condivide lo stesso processo WebView → lo stato `StylusWritingManager` è condiviso a livello di processo, non di finestra.
**Stato finale (2026-03-22)**: limite architetturale di Obsidian Mobile confermato. Il `StylusWritingManager` di Android WebView (componente compositor-level di Chromium) viene disabilitato al livello del processo WebView quando rileva l'apertura di una "drawing surface" (qualsiasi tab con canvas/SVG interattivo). Non è risolvibile via JS/CSS dall'interno dell'app.
**Workaround attuale**: lo switch "Modalità handwriting Android" nelle impostazioni. Quando attivo, i riquadri mostrano solo il badge 72px (non interferisce con il proximity detection) e l'utente deve aprire l'editor consapevolmente quando vuole disegnare, sapendo che l'handwriting-to-text nel documento verrà interrotto per quella sessione.
**Conferma esterna**: il plugin **Excalidraw** e il plugin **Handwritten Notes** hanno lo stesso identico problema — appena si apre la tab di disegno, l'handwriting-to-text si disattiva nell'intero Obsidian e non si ripristina nemmeno riavviando l'app. È un limite di Android WebView, non specifico al nostro plugin. **Non esiste soluzione lato plugin**; il compromesso dello switch è la scelta definitiva.
**Ricerca approfondita sul sorgente Chromium (2026-03-22)**:
- **Nessun bug Chromium aperto trovato** per questo problema specifico (il tracker non è scrapeable)
- **Obsidian Ink Issue #156** — ancora aperta, nessuna soluzione
- **Scoperta chiave**: il `<canvas>` da solo NON disabilita l'handwriting — non esiste codice canvas-specifico in Chromium che imposti `kInternalNotWritable`. Il trigger reale è **`touch-action: none`**: qualsiasi elemento con quella proprietà CSS fa scattare il flag `kInternalNotWritable` in `touch_action_util.cc`, disabilitando la scrittura con stilo su quell'elemento
- **Commit rilevante** (~5 mesi fa): `b06690ad` — Samsung DirectWriting disabilitato su Android 14+. Se il dispositivo è Android 14+, il percorso Samsung proprietario (closed source, potenzialmente causa di session-corruption) è già escluso
- **Ipotesi residua più probabile**: quando `DrawingEditorView` si apre, il `touch-action: none` del canvas o del suo scroll container viene propagato a livello di Android View dal WebView, e alla chiusura della tab quella configurazione non viene resettata
**Possibile prossimo test**: rimuovere completamente `touch-action: none` dal canvas e dal suo scroll container in `drawing-canvas.ts` / `editor-view.ts` e verificare se il problema persiste. Se il canvas da solo non rompe nulla (come confermato dal sorgente Chromium), potrebbe bastare rimuovere quella proprietà.
**Fonti**:
- [Chromium Stylus Handwriting README](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/stylus_handwriting/README.md)
- [ProseMirror Issue #565](https://github.com/ProseMirror/prosemirror/issues/565)
- Plugin Ink ha lo stesso problema irrisolto ([Issue #156](https://github.com/daledesilva/obsidian_ink/issues/156))
**Scoperte chiave consolidate**:
- `inputmode="none"` contamina l'intero WebView — MAI usare
- Empty SVG (300px) NON rompe handwriting; SVG con disegno (altezza > 300px per auto-expand) SÌ (sessione 2026-03-19)
- data-URI come `img.src` rompe handwriting → usare sempre URL vault + cache-bust
- Il problema è persistente tra sessioni (riavvio Obsidian) — non è corruzione di sessione temporanea
- Rimuovere completamente la nostra decorazione (nessun figlio nello span) non risolve — la causa è nell'SVG stesso o nella sua altezza
- **Il trigger è l'apertura della DrawingEditorView, non il disegno** — aprire senza toccare nulla già rompe l'handwriting (scoperta sessione 2026-03-22)
- **Anche una finestra Obsidian separata non isola il problema** — il WebView process è condiviso (scoperta sessione 2026-03-22)
**Tentativi falliti (completo)**:
| # | Approccio | Risultato |
|---|-----------|-----------|
| 16 | Rimuovere `beforeinput` listener globale | Non risolve |
| 17 | `<button>``<div role="button">` in cm-content | Non risolve |
| 18 | `<img>` → CSS `background-image` | Non risolve |
| 19 | Rimuovere `contenteditable="false"` dai wrapper CM6 + `pointer-events: none` | Non risolve |
| 20-23 | DevTools: rimuovere CE=false, touch-action, background-image, canvas dal DOM | Non risolve |
| 24 | Editor in Modal invece di tab | Non praticabile (stylus non disegna nel modal) |
| 25 | Bottone portale in `document.body` | Non risolve (canvas nella tab corrompe sessione) |
| 26 | Test console: canvas fake in `document.body` toccato con stylus | Conferma: canvas + stylus = handwriting rotto |
| 27 | Passaggio a `![[svg]]` con MutationObserver | Non risolve |
| 28 | Rimozione totale decorazione dallo span (nessun figlio aggiunto) | Non risolve |
| 29 | Rimosso `touch-action: none` da `.hwm_resize-handle` | Non risolve |
| 30 | IME reset (blur/focus su `cm-content`) in `DrawingEditorView.onClose()` | Non risolve |
| 31 | Sostituzione `<canvas>` con `<svg>` nel motore di disegno (Option C) | Non risolve — reverted |
| 32 | Apertura tab senza disegnare nulla | Conferma: il trigger è l'apertura della tab, non il disegno |
| 33 | Apertura in nuova finestra Obsidian (già fatto di default su Mobile) | Non risolve — WebView process condiviso |
---
## Completato — Bug tabella //TABLE (2026-03-24)
Bug: le righe dati della tabella venivano lasciate come testo grezzo. Il parser era corretto (83 test passati); la causa era Gemini che modificava i tag `<KEYWORD>` (riconosciuti come HTML). Risolto cambiando la sintassi delle keyword da `<KEYWORD>` a `//KEYWORD` (doppio slash), più affidabile per l'OCR di scrittura a mano. Aggiunto anche log debug in `embed.ts` che mostra il testo grezzo Gemini in un Notice (30s) quando la modalità debug è attiva.
---
## Completato — Sistema keyword OCR (2026-03-23)
- Sintassi `<KEYWORD> contenuto` (con `<>`) in `md-parser.ts`
- `normalizeMarkdownSymbols`: strip BOM/zero-width chars da Gemini, correzioni simboli markdown scritti a mano
- `expandKeywords`: 33 keyword con alias, case-insensitive, colon opzionale, multi-riga per TABLE/CODEBLOCK/MATHBLOCK
- Sezione "Keyword riconosciute dal parser OCR" collassabile nelle impostazioni
- Test autonomo `src/parser.test.ts` (77 test, eseguibile con `npx tsx src/parser.test.ts`)
---
## Ricerca effettuata — Plugin esistenti
### Nessuno fa esattamente questo. Gap confermato.
| Plugin | Cosa fa | Manca |
|--------|---------|-------|
| **Ink** (`daledesilva/obsidian_ink`) | Canvas inline nel `.md`, tldraw, penna | OCR/conversione testo (in roadmap) |
| **Handwriting to Text** (`jirayu3141`) | Foto → Gemini AI → testo nel cursore | Non è canvas inline, è workflow foto |
| **Petrify** (`jo-minjun/petrify`) | File tablet e-ink → Excalidraw/MD con OCR | Pensato per reMarkable/Boox, non canvas inline |
| **AI Image OCR** (`rootiest`) | Immagine → AI OCR → testo | Non è canvas inline |
| **Pergament** (`hobyte`) | Canvas embedded primitivo | Nessun OCR, sviluppo lento |
### Differenze rispetto a Ink (nostro riferimento)
| Ink | Il nostro plugin |
|-----|-----------------|
| tldraw (pesante, React) | Canvas API nativa (leggero, zero dipendenze extra) |
| File `.drawing` proprietari JSON | File **SVG standard** visibili ovunque |
| Nessuna conversione testo | **OCR + conversione markdown** (Fase 2) |
| React + Jotai | Vanilla TypeScript |