Memos layers #tag syntax on top of CommonMark, but there was no way to
write a literal "#NAS" — it always became a clickable tag (issue #6035).
Handle escapes lexically, the way TipTap/prosemirror-markdown do, with no
"escaped tag" node in the document model:
- Parse: marked's built-in backslash escape already turns "\#NAS" into
plain text; the read-only renderer (remark-tag) now honors the same
escape by lexing from the original source slice and falling back to the
prior value-based parse when it can't faithfully reconstruct the text.
- Serialize: a tag-aware Markdown extension (tagMarkdown.ts) backslash-
escapes a "#" that would re-parse into a tag, skipping links and code
where tags never form.
- The editor's #tag lexing moves to the canonical @tiptap/markdown
markdownTokenizer; tag-in-link skipping moves to a tree pass so the
editor and renderer agree.
The capped tag-run grammar is consolidated into utils/tag-grammar.ts
(TAG_RUN), shared by the tokenizer, the serialize-escape, and the
renderer so they can't drift. README documents why the markdown manager
is worked around in three places.
Behavior-preserving restructure of the MemoEditor subsystem for simplicity
and extensibility:
- Add a data-driven command catalog (editorCommands.ts) as the single source
for formatting verbs; the toolbar, active-state hook, and WYSIWYG handle all
derive from it, so adding a verb is a one-file change.
- Add a createSuggestionExtension factory; TagSuggestion consumes it, so new
`/` or `@` triggers reuse the shared popup in ~10 lines.
- Collapse FormattingController into a single EditorController with an optional
`formatting` capability; PlainEditor is an honest textarea fallback (no faked
formatting); replace stringly-typed isActive with typed getActiveFormats.
- Move audio-recorder state out of the reducer into useAudioRecorder, keeping a
single recorderBusy flag in the store.
- Convert the editor context to an external store (useSyncExternalStore) with
per-slice subscriptions, so typing no longer re-renders the toolbar, insert
menu, or metadata.
- Extract the shared #tag lexing grammar (tag-grammar.ts) used by both the
editor tokenizer and the remark renderer.
- Remove dead reducer actions/cases and fix a preview blob-URL leak on the
upload path.
Add tests for the command catalog, suggestion factory, and autosave.
Ctrl+A creates a whole-document AllSelection; deleting it (Backspace, Delete, or Cut) maps the AllSelection onto the now-empty paragraph instead of collapsing to a caret, so the view paints a "selected" empty block. A ProseMirror appendTransaction now collapses a leftover AllSelection to a caret after any doc-changing edit.