fix: address Copilot review on release/3.3.2

Non-ASCII glyphs (regression of the #681 class of Windows UnicodeEncodeError):
- mempalace/cli.py: "✗" → "ERROR:", "⚠" → "WARNING:", em dash → "-"
- mempalace/sweeper.py: "⚠" → "WARNING:"

Backend arg validation:
- mempalace/backends/chroma.py: `_normalize_get_collection_args` now
  raises TypeError on unexpected trailing positional args instead of
  silently dropping them — surfaces call-site bugs early.

Docs site:
- website/.vitepress/config.mts: gate Google Analytics scripts behind
  MEMPALACE_DOCS_GA_ID env var (default off). Self-hosters no longer
  get GA injected unconditionally.

Landing page SPA hygiene:
- website/.vitepress/theme/landing/useLandingEffects.js: collect all
  IntersectionObserver disconnects and removeEventListener thunks in a
  shared `cleanups` registry; drain it in `onBeforeUnmount` so observers
  and form/replay listeners don't leak across SPA navigations.
This commit is contained in:
Igor Lins e Silva
2026-04-19 18:19:28 -03:00
parent 5e9451407f
commit 65b17a6e0f
5 changed files with 37 additions and 11 deletions
+2
View File
@@ -615,6 +615,8 @@ def _normalize_get_collection_args(args, kwargs):
create = kwargs.pop("create", False) create = kwargs.pop("create", False)
if rest: if rest:
create = rest.pop(0) create = rest.pop(0)
if rest:
raise TypeError(f"unexpected positional args: {rest!r}")
if kwargs: if kwargs:
raise TypeError(f"unexpected kwargs: {sorted(kwargs)}") raise TypeError(f"unexpected kwargs: {sorted(kwargs)}")
return ( return (
+2 -2
View File
@@ -179,12 +179,12 @@ def cmd_sweep(args):
failures = result.get("failures") or [] failures = result.get("failures") or []
if failures: if failures:
print( print(
f" {len(failures)} file(s) failed to sweep see stderr / logs for details.", f" WARNING: {len(failures)} file(s) failed to sweep - see stderr / logs for details.",
file=sys.stderr, file=sys.stderr,
) )
sys.exit(2) sys.exit(2)
else: else:
print(f" Not a file or directory: {target}", file=sys.stderr) print(f" ERROR: Not a file or directory: {target}", file=sys.stderr)
sys.exit(1) sys.exit(1)
+1 -1
View File
@@ -321,7 +321,7 @@ def sweep_directory(dir_path: str, palace_path: str) -> dict:
result = sweep(str(f), palace_path, source_label=str(f)) result = sweep(str(f), palace_path, source_label=str(f))
except Exception as exc: except Exception as exc:
logger.error("sweeper: sweep failed on %s: %s", f, exc) logger.error("sweeper: sweep failed on %s: %s", f, exc)
print(f" \u26a0 sweep failed on {f}: {exc}", file=sys.stderr) print(f" WARNING: sweep failed on {f}: {exc}", file=sys.stderr)
failures.append({"file": str(f), "error": str(exc)}) failures.append({"file": str(f), "error": str(exc)})
continue continue
total_added += result["drawers_added"] total_added += result["drawers_added"]
+5 -2
View File
@@ -11,6 +11,7 @@ function normalizeBase(base?: string): string {
const docsBase = normalizeBase(process.env.DOCS_BASE || '/') const docsBase = normalizeBase(process.env.DOCS_BASE || '/')
const editBranch = process.env.DOCS_EDIT_BRANCH || 'main' const editBranch = process.env.DOCS_EDIT_BRANCH || 'main'
const gaId = process.env.MEMPALACE_DOCS_GA_ID
export default withMermaid( export default withMermaid(
defineConfig({ defineConfig({
@@ -26,8 +27,10 @@ export default withMermaid(
['meta', { property: 'og:title', content: 'MemPalace — AI Memory System' }], ['meta', { property: 'og:title', content: 'MemPalace — AI Memory System' }],
['meta', { property: 'og:description', content: '96.6% LongMemEval recall. Zero API calls. Local, free, open source.' }], ['meta', { property: 'og:description', content: '96.6% LongMemEval recall. Zero API calls. Local, free, open source.' }],
['meta', { property: 'og:image', content: `${docsBase}mempalace_logo.png` }], ['meta', { property: 'og:image', content: `${docsBase}mempalace_logo.png` }],
['script', { async: '', src: 'https://www.googletagmanager.com/gtag/js?id=G-PPQE4Z7P1K' }], ...(gaId ? [
['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', 'G-PPQE4Z7P1K');`], ['script', { async: '', src: `https://www.googletagmanager.com/gtag/js?id=${gaId}` }],
['script', {}, `window.dataLayer = window.dataLayer || [];\nfunction gtag(){dataLayer.push(arguments);}\ngtag('js', new Date());\ngtag('config', '${gaId}');`],
] as const : []),
], ],
themeConfig: { themeConfig: {
@@ -1,6 +1,10 @@
import { onMounted, onBeforeUnmount } from 'vue' import { onMounted, onBeforeUnmount } from 'vue'
export function useLandingEffects() { export function useLandingEffects() {
// Shared cleanup registry — IIFEs push disconnect/removeEventListener thunks
// here so onBeforeUnmount can tear everything down on SPA nav.
const cleanups = []
onMounted(() => { onMounted(() => {
if (typeof document === 'undefined') return if (typeof document === 'undefined') return
@@ -25,7 +29,7 @@ onMounted(() => {
if (text != null) msg.textContent = text if (text != null) msg.textContent = text
} }
form.addEventListener('submit', async (e) => { const onSubmit = async (e) => {
e.preventDefault() e.preventDefault()
if (form.classList.contains('is-success') || form.classList.contains('is-pending')) return if (form.classList.contains('is-success') || form.classList.contains('is-pending')) return
@@ -70,11 +74,17 @@ onMounted(() => {
button.disabled = false button.disabled = false
input.disabled = false input.disabled = false
} }
}) }
// Clear error state as soon as the user edits const onInput = () => {
input.addEventListener('input', () => {
if (form.classList.contains('is-error')) setState(null, '') if (form.classList.contains('is-error')) setState(null, '')
}
form.addEventListener('submit', onSubmit)
input.addEventListener('input', onInput)
cleanups.push(() => {
form.removeEventListener('submit', onSubmit)
input.removeEventListener('input', onInput)
}) })
}) })
})() })()
@@ -102,6 +112,7 @@ onMounted(() => {
}) })
}, { rootMargin: '0px 0px -80px 0px' }) }, { rootMargin: '0px 0px -80px 0px' })
items.forEach(el => io.observe(el)) items.forEach(el => io.observe(el))
cleanups.push(() => io.disconnect())
})() })()
/* ---------- Forgetting demo ---------- */ /* ---------- Forgetting demo ---------- */
@@ -369,17 +380,27 @@ onMounted(() => {
} }
} }
if (replayBtn) replayBtn.addEventListener('click', () => { const onReplayClick = () => {
resetAll() resetAll()
armObservers() armObservers()
}) }
if (replayBtn) replayBtn.addEventListener('click', onReplayClick)
armObservers() armObservers()
cleanups.push(() => {
disconnectObservers()
if (replayBtn) replayBtn.removeEventListener('click', onReplayClick)
})
})() })()
}) })
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (typeof document === 'undefined') return if (typeof document === 'undefined') return
document.body.classList.remove('mempalace-active') document.body.classList.remove('mempalace-active')
while (cleanups.length) {
const fn = cleanups.pop()
try { fn() } catch (_) { /* swallow — teardown best-effort */ }
}
}) })
} }