Pending: 0 Building: 0 Running: 3 Failed: 269
Created Date Type Name Commit Description State Age Up Time Life Time Action
merged [IMP] viin_brain: Brain editor UI/UX polish (#44) [IMP] viin_brain: Brain editor UI/UX polish - white canvas, AA contrast, inline icon, symmetric padding, mobile collapse, declutter chrome, a11y labels (#44) Addresses issue #44 (P16 follow-up). Includes code-review cleanup: shadow token, deduped chip styling, t-foreach overflow menu, centralised property a11y attrs, structural header layout, and label-for only on input-bearing property types. Failed
merged [IMP] viin_brain: Brain editor UI/UX polish (#44) [IMP] viin_brain: Brain editor UI/UX polish - white canvas, AA contrast, inline icon, symmetric padding, mobile collapse, declutter chrome, a11y labels (#44) Killed Not finished
merged [IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration) [DOC] docs,AGENTS: ADR-010 editor framework + reconcile docs with shipped P16 ADR-010 Editor Extension Framework + WI-007/WI-008; reconcile brain/ai architecture, ai-bridge, ui-design, conventions, roadmap and decision index with the shipped code; AGENTS module map adds viin_ai_editor + engine desc. Killed
merged [IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration) [IMP] P16 review supplement: fix review findings + Brain editor UX polish (#2) * [FIX] viin_ai_ops: null-guard ListRenderer.setDefaultColumnWidths against intermittent DOM race Intermittent TypeError "Cannot read properties of null (reading 'style')" in core Odoo 17 ListRenderer.setDefaultColumnWidths() surfaces on the Action Proposals list when the view re-renders with zero records (isEmpty toggle races thead commit to DOM, querySelector returns null). Adds a defensive monkey-patch in viin_ai_ops that overrides the method with an identical but null-safe implementation: if headerEl is null the column iteration step is skipped instead of crashing. This matches the upstream fix strategy applied in Odoo 18 via useMagicColumnWidths. BACKPORT FROM: Odoo 17 core list_renderer.js (upstream fixed in 18.0) REMOVE WHEN UPGRADING TO: 18.0 * [FIX] viin_ai_editor: surface UserError message in AI block error state Odoo 17 RPCError shape wraps the real UserError text in err.data.message while err.message is always the opaque "Odoo Server Error" wrapper. The catch in AiBlock._generate now reads err.data.message first so users see the actionable UserError (e.g. "No AI agent configured") instead of the unhelpful wrapper. - ai_block.js: prioritise err.data.message || err.message || fallback _t() - conventions.md: add JSON-RPC error handling convention (SSOT: chat_panel.js) - test_ai_block_state_machine.js: add 2 cases covering RPCError shape and plain-Error fallback * [IMP] viin_web_editor,viin_brain,viin_ai_brain: dedup powerbox native commands (WI-007) Introduce a generic suppress-native registry in viin_web_editor so downstream modules can declaratively remove native OdooEditor/Wysiwyg powerbox commands that they replace, without the engine hardcoding any consumer-specific names. Changes: - viin_web_editor/powerbox_registry.js: export new registry viin_web_editor.powerbox_suppress_native with suppressor shape {matcher, contextGate?} - viin_web_editor/wysiwyg_patch.js: filter upCommands (native) through all registered suppressors before concatenating extra commands; exceptions inside suppressors are caught per-entry (fail-safe) - viin_brain/slash_commands.js: register five suppressors (headings/quote/code/ bullet/numbered, all context-gated to .o_brain_editor_body_edit); fix brain_bullet_list and brain_numbered_list to use toggleList (identical to native, preserves toggle-off semantics) - viin_ai_brain/powerbox_commands.js: register suppressor for native ChatGPT (fa-magic) gated on bridge.available=true - docs/decisions/wi-007-powerbox-suppress-native.md: new append-only WI, Refines ADR-010 * [FIX] viin_ai_brain: multi-company filter agent lookup (B1) Extract the Knowledge Base Assistant agent lookup into a single SSOT helper `viin_ai_brain/controllers/common.py::find_kba_agent()` that enforces `company_id in [env.company.id, False]` on every search. Before this fix wysiwyg.py `_get_agent()` searched by name only with no company filter, leaking cross-tenant agent records. ai_block.py already had the filter but in a local function. Both controllers now import `find_kba_agent` from `controllers/common.py`, eliminating the duplicate logic and the security gap. * [IMP] viin_ai_brain: topic-based agent lookup + CTA (MED-2) Update `controllers/common.py::find_kba_agent` to resolve the KBA agent via the `viin_ai_brain.topic_brain_assistant` topic xmlid instead of the display name. This makes lookup name-independent: renaming the agent for i18n or branding purposes does not break any controller path. When no agent with the topic is found, raises UserError with an actionable CTA pointing to AI > Configuration > Agents (viin_ai_agent.action_viin_ai_agent). Frontend changes: - ai_bridge_service.js: pass topicXmlId instead of hard-coded topicName for ChatPanel so the chat panel can resolve the topic server-side. - ai_editor_backend_service.js: remove "Knowledge Base Assistant" hard-coded fallback for agent_name; server is the SSOT (empty string sentinel on RPC success avoids masking real lookup failures). Tests (Odoo native TransactionCase, @tagged post_install): - TestKbaAgentLookupMultiCompany: B1 isolation - company A env cannot resolve company B's KBA agent; global agents (company_id=False) are visible from any company; missing-company agent raises UserError+CTA. - TestKbaAgentTopicLookup: rename-safe lookup (MED-2); detaching topic raises UserError+CTA; wysiwyg endpoint surfaces CTA end-to-end. - Updated TestBrainBridgeCommon to attach the KBA topic to the fixture agent, and updated _ensure_brain_agent in tour tests likewise. - Updated test_commit_attribution_raises_when_no_kba_agent: detach topic (not rename) to simulate missing agent under topic-based lookup. Verified: 0 failed, 0 error of 57 tests (wave_wid_test ephemeral DB). * [IMP] viin_brain: fix AI badge wording to reflect installed-not-ready semantics Badge text changed from "AI ready" to "AI features available" and tooltip updated to "AI features are installed - configure a provider & agent to use them." to avoid implying the AI is operational before a provider and agent are configured (Option B wording-only fix, WI-E). * [DOC] docs: WI-008 PR #43 deferred follow-ups tracking + roadmap corrections Create docs/decisions/wi-008-pr43-deferred-followups.md (append-only WI): - WI-F (MED-3): Provider Test Connection button -> viin_ai_base Layer 0 backlog - WI-G: L2 active-default policy (needs ADR-011), L3 dashboard CTA (W12 P-OPS-4), L4 admin empty-state guidance (Phase 5/onboarding) - WI-007-onboarding: Phase 5 ADR + wizard (M4 gate); seed agent stub explicitly excluded (model_id required+restrict constraint); CTA error already done in WI-D2 - B2: CRDT pitch guidance - interim relay is NOT OT real-time collab (ADR-002 intact) - B3: SQL template count corrected to 15 (roadmap said 11 pre-viin_ai_crm baseline; review cited 13; correct per CEO 2026-06-06 = 15) Update docs/roadmap.md: - Fix SQL template count 11 -> 15 with WI-008 B3 reference - Add Layer 0 backlog note after M1 gate (WI-F, WI-G L2) - Add AI Onboarding wizard item to Phase 5 W16-W19 (gated M4) - Add WI-005, WI-006, WI-008 rows to ADR/WI cross-reference table - Mark ADR-010 DONE 2026-06-04; add ADR-011 candidate for active-default policy Update docs/decisions/README.md: - Add wi-008 row to WI index - Update date to 2026-06-06 * [FIX] viin_ai_brain,viin_ai_editor,viin_brain,viin_web_editor: eslint prettier formatting on WI changes * [REF] viin_ai_brain: Command.* in WI-D tests + drop dead chat prop (review LOW) * [FIX] viin_brain: powerbox suppressor context-gate timing (Phase V blocker) Root cause: _getPowerboxOptions() runs synchronously inside startEdition(). The DOM markers (.o_brain_editor_body_edit[data-page-id]) are applied in the startWysiwyg callback AFTER startEdition() resolves, so _isBrainContext() always returned false during powerbox build - all 5 suppress-native rules were no-ops, causing 7 native commands to appear twice (Heading 1/2/3, Text, Numbered list, Quote, Link). Fix: set wysiwyg.__brainPageContext = true on the wysiwyg instance BEFORE calling startEdition() in page_editor.js. Update _isBrainContext() to check this early-signal flag (Tier 1) before falling back to the DOM closest() check (Tier 2). Flag is Brain-PageEditor-only; mail/website editors are unaffected. Bonus: deduplicate powerbox "AI" category registrations. Three WI phases (WI-A1, WI-B3, WI-007) each registered a separate category key all named "AI", producing three UI separators. Guard each registration behind a check for all known AI category keys so only one "AI" category appears in the powerbox when both viin_ai_editor and viin_ai_brain are installed. * [FIX] viin_web_editor,viin_brain,viin_ai_brain: suppress native powerbox at merge point + Text/Link + AI category (Phase V) - wysiwyg_patch.js: add Tier 2 powerboxFilters injection to suppress OdooEditor-level commands (Heading 1/2/3, Bulleted list, Numbered list, Text) that bypass _getPowerboxOptions and cannot be caught by Tier 1 alone. Use _viinCustom sentinel to distinguish consumer commands from native ones so suppressors never accidentally remove Brain's own commands that share the same fontawesome icon. - slash_commands.js: add viin_brain.suppress_text (fa-paragraph) and viin_brain.suppress_link (fa-link) suppressors for the native Text and Link/Button commands that brain_paragraph and brain_external_link replace. - brain_editor_registry_commands.js: document that key "brain_ai" is intentional (viin_ai_editor uses .add("AI") unconditionally; using the same key here would throw on duplicate). Powerbox.open() deduplicates categories by name at runtime. - powerbox_commands.js (viin_ai_brain): same documentation for key "brain_ai_write". - wi-007-powerbox-suppress-native.md: update to describe two-tier architecture, add Text/Link suppressor table rows, add AI category safety analysis. * [FIX] viin_ai_brain: group AI powerbox commands under single category (Phase V) Root cause: _t("AI") at module-evaluation time returns LazyTranslatedString *objects* (extends String). Different _t() calls across modules produce different object instances never === each other. Powerbox._groupCommands() matches command.category === category.name with strict equality, so the match always fails for cross-module LazyTranslatedString instances. Each AI command fell into the remaining-categories path and got its own "AI" header, rendering 3 separate "AI" separators instead of one. Fix: use plain string literal "AI" (not _t("AI")) for both category name and command category across all 3 AI-contributing modules. Plain strings compare correctly with ===. "AI" is locale-invariant so no i18n value is lost. All 3 modules now register under canonical key "AI": - viin_ai_editor: add("AI", ..., {force:true}) - prevents DuplicatedKeyError when brain/ai_brain registers first - viin_ai_brain, viin_brain: guarded by !contains("AI") - avoid overwrite warning when viin_ai_editor loads first WI-007 decision doc updated to document the real root cause and the fix. * [FIX] viin_brain: vault page resequence no longer clears the tree (P16 review) Root cause: _reorderPage called _invalidateParent(null) after the ORM writes. _invalidateParent deletes childrenByParent.get(parentId) then re-fetches only when parentId is in expandedIds. The root parent (null) is never in expandedIds, so the root list was permanently deleted and the sidebar showed "No page in this vault" until the user navigated away or reloaded the page. Server data was saved correctly — only the UI was affected. Fix: optimistic reorder. Swap sequence values directly in the in-memory pageCache before awaiting the ORM writes, re-sort the affected parent's child-id array in childrenByParent using the updated sequences, then bump the flatRows cache. The list is never cleared between the drop event and the server response. A try/catch around the ORM writes restores consistency via _invalidateParent + an explicit _loadRoots call only in the error path. A new QUnit unit test (no-mount harness, same pattern as the WI-8 suite) pins the regression: after _reorderPage the root list must still exist, contain both page ids, and be re-sorted by the new sequences. * [IMP] viin_brain: Brain editor UX polish - editor border, powerbox default style, placeholder hints, clickable breadcrumb (P16 review) Issue 2 (editor border): fix specificity loss in .o_brain_editor_body_edit note-editable border override. Add compound selector &.note-editable + !important backstop so Brain wysiwyg canvas never shows the textarea-like border from web_editor.backend_assets_wysiwyg regardless of stylesheet load order. Issue 3 (powerbox style): remove Brain-specific powerbox SCSS overrides (_powerbox.scss). The custom grey background / washed-out description text diverged from Odoo default. Revert to Odoo default white-background crisp powerbox. Command injection via wysiwyg_patch.js / powerboxCommandsRegistry is completely unaffected - only visual overrides removed. Issue 4 (placeholder hint): wire OdooEditor native placeholder option in _buildWysiwygOptions() so empty pages show "Type / for commands, [[ to link a page, @@ to mention" hint via .oe-hint::before. Also add o_brain_editor_placeholder class to the editable in startWysiwyg() as CSS fallback for :empty:not(:focus)::before defined in _app_shell.scss. Issue 5 (breadcrumb + header lean): make vault breadcrumb segment clickable (role=button, tabindex=0, cursor pointer, hover highlight, onBreadcrumbVaultClick handler calls onVaultSelect prop bound from BrainApp.onVaultSelect). Tighten header layout: margin-bottom 16px->10px, breadcrumb mb 8px->4px, sep padding 0 6px->0 4px, title font 28px->26px, icon 24px->22px, padding 4px->2px 0. * [FIX] viin_brain: show editor placeholder hint (CSS cascade fix, P16 review) Drag-handle rule `> p::before { content: "⋮⋮" }` was overriding `.oe-hint::before { content: attr(placeholder) }` due to equal specificity and source order. Fix: narrow the drag-handle selector to `> p:not(.oe-hint)` so the hint paragraph is excluded from the drag-handle block entirely. Also add explicit `content: attr(placeholder)` to the `.oe-hint::before` rule to ensure it always wins regardless of cascade order, and bump opacity from 0.38 to 0.45 for better readability. * [FIX] viin_brain: eslint prettier formatting on P16 UI fixes * [FIX] viin_brain,viin_ai_brain: scope Brain powerbox commands to Brain editor context (P16 review) Brain-specific powerbox commands were registered globally and leaked into every editor that loads the powerbox patch (mail templates, CRM description, website builder, etc.), causing UI noise and potential runtime errors when Brain-specific callbacks (wikilink, embed, database, record mention) fire outside a Brain page. Fix: add isDisabled(wysiwyg) => !isBrainContext(wysiwyg) to all Brain-specific commands so they are hidden by the Powerbox engine outside a Brain editor. isBrainContext uses a two-tier check (wysiwyg.__brainPageContext early-signal flag + DOM .o_brain_editor_body_edit[data-page-id] fallback) identical to the existing WI-C suppress-native contextGate. SSOT: extract shared predicate to viin_brain/static/src/utils/brain_context.js (web.assets_backend eager bundle, no @web_editor dep). Three consumers: - slash_commands.js (wysiwyg lazy bundle) - all 14 Brain commands gated - brain_editor_registry_commands.js (eager) - brain_ai_block stub gated - viin_ai_brain/registries/powerbox_commands.js - brain_ai_write + brain_ai_chat now require both Brain context AND bridge.available brain_ai_write / brain_ai_chat: previous _isDisabledWhenNoBridge only checked bridge availability; these commands also leaked into non-Brain editors when bridge was active. New _isDisabledWhenNoBridgeOrOutsideBrain combines both. Not gated: viin_ai_editor ai_block command is a general AI editor feature (not Brain-specific); its isDisabled already gates on backend.available. ai_block_conflict_dedup.js WI-C dedup mechanism preserved unchanged. WI-007 suppress-native contextGate logic preserved unchanged. * [FIX] viin_ai_editor: eslint prettier trailing comma * [FIX] viin_ai_brain: address Codex review on PR #43 supplement Two P2 findings from the automated Codex review: 1. find_kba_agent preferred global over company-specific agents. order='company_id desc' sorts SQL NULLs (global, company_id=False) FIRST under PostgreSQL DESC, so a company with its own KBA agent was still routed through the shared global one. Replaced with a two-step preference (company-specific search first, global fallback) - DB-agnostic and provably correct. Added TestKbaAgentCompanyPreference regression tests (company-specific wins; global still used as fallback). 2. Native ChatGPT (fa-magic) powerbox command was suppressed in EVERY editor once viin_ai_brain was installed, because the suppressor gated only on bridge.available (globally true). In non-Brain editors (mail/CRM/website) the replacement brain_ai_write is itself disabled, leaving no AI write command. Added isBrainContext(wysiwyg) to the suppression contextGate so the native command is only replaced inside Brain editors. Failed
merged [IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration) [FIX] viin_brain: restore pylint-odoo consider-merging-classes-inherited disable Reverts the removal in b93db89, which was based on a faulty reproduction against VANILLA Odoo (no pylint-odoo loaded -> the inline disable read as unknown-option-value W0012). The Viindoo Runbot lint is NOT Odoo-core test_lint: it runs the tvtmaaddons test_pylint module, which loads pylint_odoo and ENABLES consider-merging-classes-inherited (R8180). So the disable is required: viin.brain.page is intentionally re-opened in both viin_brain_page_tag.py and viin_brain_page_extension.py for readability, and R8180 asks to merge them. Verified against the period-correct Odoo 17 stack (pylint 2.15.10 / astroid 2.13.5 / pylint-odoo 8.0.22) using the Runbot module's exact ENABLED_CODES: with the disable restored, all 21 viin modules report 0 findings (R8180 suppressed). Runbot build 222101 failed on exactly this one message (1 failed of 24 tests). Killed
merged [IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration) [FIX] viin_web_editor,viin_brain,viin_ai_editor,viin_ai_brain: Runbot test_lint/test_pylint green Reproduced the Runbot failures locally against Odoo 17 test_lint (period-correct pylint 3.3.7) instead of guessing, and fixed the real root causes: - test_pylint (Python): viin_brain/models/viin_brain_page_tag.py carried '# pylint: disable=consider-merging-classes-inherited'. That message belongs to the pylint-odoo plugin, which Odoo core test_pylint does NOT load, so the inline disable resolves to unknown-option-value (W0012) - not suppressed by '--disable=all,useless-option-value' - making pylint exit non-zero. The message is not even in test_pylint's enable list, so the disable was inert. Removed it. Verified pylint exit 0 across all 21 viin modules. - test_eslint (JS): 417 prettier/prettier errors across P16 JS under the repo .eslintrc.json (extends plugin:prettier/recommended, tabWidth 4, printWidth 100). The husky hook only formats staged files, so P16 files committed earlier were never formatted. Ran eslint --fix; verified 0 errors against both the repo prettier config and Odoo core eslintrc. - test_manifests: viin_ai_editor set 'auto_install': False, which equals the default manifest value (assertNotEqual hard-fail). Removed the key. No behavior change: JS edits are pure formatting; Python edits delete an inert key and an inert lint pragma. package-lock.json kept at baseline. Failed
merged [IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration) [FIX] viin_web_editor,viin_brain,viin_ai_editor,viin_ai_brain: lint clean for Runbot (prettier/eslint format, unused vars, manifest, pot, flake8) Failed
merged [IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration) [FIX] viin_brain: repair slash-focus tour (AI category priority outranks Brain) ROOT CAUSE (tour fragility, not product code regression): viin_ai_editor registers an "AI" powerbox category at priority 95 and an "AI block" command at priority 90. The "Brain" category is registered at priority 90. The OdooEditor Powerbox sorts categories by priority desc then name asc, so "AI" (p=95) renders before "Brain" (p=90). Pressing Enter after a bare "/" selected the "AI block" command instead of "Heading 1", and the assertion :has(h1,h2,h3) failed. Evidence: failure screenshot showed the AI block component rendered (the "AI block - Empty - type a prompt or pick a template" UI) instead of an h1. The Brain heading-via-slash command itself works correctly when explicitly selected; the tour was testing an incidental ordering assumption. FIX (tour fix, not product code): Changed the powerbox step to type "/heading" instead of bare "/" before pressing Enter. After insertText("/heading") + a keyup event, Powerbox. _onKeyup() runs patienceDiff and filters filteredCommands to only the three Brain heading commands (Heading 1 p=100, Heading 2 p=90, Heading 3 p=80). "Heading 1" is then the active/first command. Tightened the final assertion from :has(h1,h2,h3) to :has(h1) since the filter + priority guarantee h1. FAILURE 2 (TestBrainTimelineTour): already green in the worktree; 0 failed on isolation re-run. No code change needed. Wikilink tour: confirmed still green (3/3 pass, 0 errors). Failed
merged [IMP] P16: Editor Extension Framework - modular Brain/AI editor (OdooEditor migration) [FIX] viin_ai_editor: valid XML comments in demo data (double-hyphen) Failed
merged [DOC] ADR-010 Editor Extension Framework (modular Brain/AI editor) — Proposed [DOC] docs: ADR-010 Editor Extension Framework (modular Brain/AI editor) Refine ADR-003 with the structural HOW for the OdooEditor migration (P16): - Keep viin_web_editor as the neutral propagation base (patch HtmlField/ Wysiwyg once, driven by global registries) - it is the keystone that lets Brain and AI editor plugins depend on it without coupling to each other (AGENTS.md cam ky #2). Deleting it was considered and rejected. - Move the AI block out of viin_brain into a new AI-cluster module viin_ai_editor (generic, works in any html field); bridge viin_ai_brain wires AI output to Brain attribution + resolves Brain x AI conflicts. - Grounded in verified Odoo 17 web_editor seams (options.plugins, _getPowerboxOptions, HtmlField.wysiwygOptions, no native NodeView, setupCollaboration, '/'-only input rule). Tracker odooeditor-migration-progress.md re-organised from 23-features-in-Brain to module-split + a 4-phase wave plan (framework -> plugins -> bridge -> verify+visual) with odoo-ui-reviewer visual gates. Status Proposed - awaiting sign-off before execution. Killed Not started Not finished
merged [DOC] ADR-010 Editor Extension Framework (modular Brain/AI editor) — Proposed [DOC] docs: ADR-010 Editor Extension Framework (modular Brain/AI editor) Refine ADR-003 with the structural HOW for the OdooEditor migration (P16): - Keep viin_web_editor as the neutral propagation base (patch HtmlField/ Wysiwyg once, driven by global registries) - it is the keystone that lets Brain and AI editor plugins depend on it without coupling to each other (AGENTS.md cam ky #2). Deleting it was considered and rejected. - Move the AI block out of viin_brain into a new AI-cluster module viin_ai_editor (generic, works in any html field); bridge viin_ai_brain wires AI output to Brain attribution + resolves Brain x AI conflicts. - Grounded in verified Odoo 17 web_editor seams (options.plugins, _getPowerboxOptions, HtmlField.wysiwygOptions, no native NodeView, setupCollaboration, '/'-only input rule). Tracker odooeditor-migration-progress.md re-organised from 23-features-in-Brain to module-split + a 4-phase wave plan (framework -> plugins -> bridge -> verify+visual) with odoo-ui-reviewer visual gates. Status Proposed - awaiting sign-off before execution. Killed Not finished
open [DOC] Reconcile ADR-002 CRDT with shipped interim relay (OT-final after P16) [DOC] docs: reconcile ADR-002 CRDT with shipped interim relay Recon + Odoo native test (TestCollabStepBuffer/Controller + TestBrainRealtimeCollab -> 0 failed of 15) cho thấy CRDT thực tế đã ship ~90% qua launch-hardening (WI-9 snapshot-relay + WI-12 presence + WI-13 flag), KHÔNG phải 'sắp làm W7-W11' như roadmap/ADR-002 cũ. - ADR-002: thêm §Implementation Status — interim snapshot-relay đã ship + verified; Decision (Option 1 OdooEditor native OT) GIỮ NGUYÊN làm đích; OT-final re-sequenced SAU P16 (cần PageEditor wrap OdooEditor). Ghi rõ gap: relay step.html_snapshot, chưa gọi setupCollaboration, chưa set collaborative=true. - roadmap + brain/README: phản ánh interim DONE sớm, OT-final sau P16. Docs-only; không đụng code. Khớp quyết định: giữ mục tiêu OT thật. Killed
merged [DOC][TEST] Brain stabilization: CRDT v1 + ADR-003 OdooEditor + WI-006 daily-note ACL deny [TEST] viin_brain: close 7 residual daily-note ACL cells as deny (WI-006) Convert 7 @_odoo_xfail conformance-gap tests to hard DENY assertions (AccessError): viewer/commenter create (AC-9.3a/b, 14.3a), commenter message_post (AC-14.5a/b), group-resolved editor write (AC-19.2b), vault-admin unlink (AC-24.4) on own daily note. No production/security change — deny-preserving per WI-006. Remove dead _odoo_xfail helper. Verified: odoo-bin --test-enable on the 4 daily-note ACL classes -> 0 failed, 0 error of 45 tests. Killed
merged [FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3) [FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3) - T-2 privacy: daily notes can never be shared via link (ValidationError in _check_page_shareable); share comment state no longer hardcoded (honors comment_auto_approve). - T-3 ACL: complete the 40-cell matrix in tests — non-owner (incl brain/vault admin) cannot read/write another user's daily note; commenter cannot write own daily body. No rule change (rule_page_daily_global already enforces). - T-1 concurrency: deterministic version token (isoformat ms) + re-enable first-save optimistic-concurrency check in page_editor (realtime untouched). - Tests: viin_web_editor powerbox-registry QUnit, trigger-regex edge cases, block_parser round-trip tour + wrapper. - Docs: CHANGELOGs, brain/security.md ACL matrix, decisions/wi-005 (privacy-first). Killed
merged [FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3) [FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3) - T-2 privacy: daily notes can never be shared via link (ValidationError in _check_page_shareable); share comment state no longer hardcoded (honors comment_auto_approve). - T-3 ACL: complete the 40-cell matrix in tests — non-owner (incl brain/vault admin) cannot read/write another user's daily note; commenter cannot write own daily body. No rule change (rule_page_daily_global already enforces). - T-1 concurrency: deterministic version token (isoformat ms) + re-enable first-save optimistic-concurrency check in page_editor (realtime untouched). - Tests: viin_web_editor powerbox-registry QUnit, trigger-regex edge cases, block_parser round-trip tour + wrapper. - Docs: CHANGELOGs, brain/security.md ACL matrix, decisions/wi-005 (privacy-first). Killed Not finished
merged [FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3) [FIX] viin_brain,viin_web_editor: Brain core launch-hardening (T-1/T-2/T-3) - T-2 privacy: daily notes can never be shared via link (ValidationError in _check_page_shareable); share comment state no longer hardcoded (honors comment_auto_approve). - T-3 ACL: complete the 40-cell matrix in tests — non-owner (incl brain/vault admin) cannot read/write another user's daily note; commenter cannot write own daily body. No rule change (rule_page_daily_global already enforces). - T-1 concurrency: deterministic version token (isoformat ms) + re-enable first-save optimistic-concurrency check in page_editor (realtime untouched). - Tests: viin_web_editor powerbox-registry QUnit, trigger-regex edge cases, block_parser round-trip tour + wrapper. - Docs: CHANGELOGs, brain/security.md ACL matrix, decisions/wi-005 (privacy-first). Failed
merged [DOC] root: AI agent onboarding guides (AGENTS/CLAUDE/GEMINI) [DOC] root: add AI agent onboarding guides (AGENTS/CLAUDE/GEMINI) SSOT in AGENTS.md (read directly by Codex); CLAUDE.md and GEMINI.md @import it plus per-CLI notes. Positions ai17 as two independent products bridged modularly: Viindoo Brain (knowledge OS — Notion for humans + Obsidian-style second brain for AI agents) and Viindoo AI (agentic orchestration OS). Captures must-read docs, core security prohibitions, git/worktree discipline, Runbot test model. Shared-repo hygiene: guides contain NO machine/personal-local info. AGENTS.md adds prohibitions 18-21 — never commit absolute home paths, internal infra, per-developer AI config (MCP/skills/local models), or secrets; keep per-developer config outside the repo; self-check diffs before PR. CLAUDE.md/GEMINI.md kept generic and portable. Killed Not finished
merged [DOC] root: AI agent onboarding guides (AGENTS/CLAUDE/GEMINI) [DOC] root: add AI agent onboarding guides (AGENTS/CLAUDE/GEMINI) SSOT in AGENTS.md (read directly by Codex); CLAUDE.md and GEMINI.md @import it plus per-CLI notes. Positions ai17 as two independent products bridged modularly: Viindoo Brain (knowledge OS — Notion for humans + Obsidian-style second brain for AI agents) and Viindoo AI (agentic orchestration OS). Captures must-read docs, core security prohibitions, git/worktree discipline, Runbot test model. Killed Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [IMP] viin_brain,viin_web_editor: Brain core UI/UX polish wave Squashed integration of the Brain core polish wave (branch wave/integration-brain-polish-w1): runtime bug fixes, theme repair, a11y/responsive, the form-sidebar quick-create drawer reworked onto the native web_editor editor, plus JS/behaviour tests and doc sync. Scope is viin_brain + viin_web_editor only; rebased on origin/17.0. Added - Quick-create drawer note body now mounts the native web_editor Wysiwyg (OdooEditor), lazy-loaded from web_editor.backend_assets_wysiwyg, working on in-memory HTML (no persisted page) so the create-on-save/discard flow is preserved; body saved into content_html. First surface delegated to the audited editor per ADR-003 Path B (PageEditor migration stays P16). - Responsive 3-pane layout (_responsive.scss): tablet 2-pane with the context pane as an overlay; phone single-pane with a slide-in sidebar drawer and ellipsised title. - A11y: <main role="main"> landmark, page-title aria-label, disabled "Related" tab aria-disabled + tabindex=-1; header hamburger toggles the sidebar; timeline icon corrected. Changed - Form sidebar lists notes LINKED to a record, not only inline @-mentions: _brain_mentioning_page_ids unions viin.brain.link record-mentions with viin.brain.page.record.link rows, and _compute_brain_page_count uses the same union via a distinct page-id set (no double count) with ACL filtering. - "Link a record" replaced by a friendly two-step "Link a document" picker (core ModelSelector + RecordSelector); model:id output contract unchanged. - Filter pills compacted to a single scrolling row; panel/graph bars wrap instead of overflowing in the ~300px context pane. Fixed - Theme token system repair (theme_tokens.scss): Odoo 17 emits no --bs-* runtime properties and the prior self-referential shims formed a CSS cycle resolving empty (theme-blind UI); redefined non-self-referentially from live Odoo vars with literal fallbacks. - OWL this-binding crashes in bare inline arrow handlers (drawer vault / entry-kind clicks, calendar view, moderation panel). - Form sidebar layout is resize-reactive: ui service useState-wrapped so layoutMode recomputes on viewport change. - Share dialog reveals the public URL on save + .bind toggle; template dialog body moved to the Dialog default slot; sidebar label live-updates on rename via a brain-page-renamed bus event; removed the hardcoded "P12" badge; badges migrated to text-bg-secondary. - viin_web_editor: removed the spike powerbox demo commands (viin_spike_hello / viin_spike_block + brain category); registries ship empty; embedded_component_host docstring repointed to native_commands.js. Tests - Drawer tour (crash guards + native Wysiwyg mount + Save) with a Python runner asserting the server outcome; BrainFormSidebar layout + resize reactivity unit tests; backend tests for the linked-notes union. Verified on a clean pg16 DB: drawer tour green, backend suites green. Docs - CHANGELOG [Unreleased] entries (viin_brain + viin_web_editor); README "what's new"/responsive/sidebar updates; contenteditable_policy.md reconciled (Path-B delegated surface, status, change history). Killed
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [IMP] viin_brain,viin_web_editor: Brain core UI/UX polish wave Squashed integration of the Brain core polish wave (branch wave/integration-brain-polish-w1): runtime bug fixes, theme repair, a11y/responsive, the form-sidebar quick-create drawer reworked onto the native web_editor editor, plus JS/behaviour tests and doc sync. Scope is viin_brain + viin_web_editor only; rebased on origin/17.0. Added - Quick-create drawer note body now mounts the native web_editor Wysiwyg (OdooEditor), lazy-loaded from web_editor.backend_assets_wysiwyg, working on in-memory HTML (no persisted page) so the create-on-save/discard flow is preserved; body saved into content_html. First surface delegated to the audited editor per ADR-003 Path B (PageEditor migration stays P16). - Responsive 3-pane layout (_responsive.scss): tablet 2-pane with the context pane as an overlay; phone single-pane with a slide-in sidebar drawer and ellipsised title. - A11y: <main role="main"> landmark, page-title aria-label, disabled "Related" tab aria-disabled + tabindex=-1; header hamburger toggles the sidebar; timeline icon corrected. Changed - Form sidebar lists notes LINKED to a record, not only inline @-mentions: _brain_mentioning_page_ids unions viin.brain.link record-mentions with viin.brain.page.record.link rows, and _compute_brain_page_count uses the same union via a distinct page-id set (no double count) with ACL filtering. - "Link a record" replaced by a friendly two-step "Link a document" picker (core ModelSelector + RecordSelector); model:id output contract unchanged. - Filter pills compacted to a single scrolling row; panel/graph bars wrap instead of overflowing in the ~300px context pane. Fixed - Theme token system repair (theme_tokens.scss): Odoo 17 emits no --bs-* runtime properties and the prior self-referential shims formed a CSS cycle resolving empty (theme-blind UI); redefined non-self-referentially from live Odoo vars with literal fallbacks. - OWL this-binding crashes in bare inline arrow handlers (drawer vault / entry-kind clicks, calendar view, moderation panel). - Form sidebar layout is resize-reactive: ui service useState-wrapped so layoutMode recomputes on viewport change. - Share dialog reveals the public URL on save + .bind toggle; template dialog body moved to the Dialog default slot; sidebar label live-updates on rename via a brain-page-renamed bus event; removed the hardcoded "P12" badge; badges migrated to text-bg-secondary. - viin_web_editor: removed the spike powerbox demo commands (viin_spike_hello / viin_spike_block + brain category); registries ship empty; embedded_component_host docstring repointed to native_commands.js. Tests - Drawer tour (crash guards + native Wysiwyg mount + Save) with a Python runner asserting the server outcome; BrainFormSidebar layout + resize reactivity unit tests; backend tests for the linked-notes union. Verified on a clean pg16 DB: drawer tour green, backend suites green. Docs - CHANGELOG [Unreleased] entries (viin_brain + viin_web_editor); README "what's new"/responsive/sidebar updates; contenteditable_policy.md reconciled (Path-B delegated surface, status, change history). Failed
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [DOC] viin_brain,viin_web_editor: sync docs with the UI/UX polish wave The wave shipped substantial behaviour/architecture changes with no doc updates (except a partial contenteditable_policy edit). Two read-only drift analyses (one per module) mapped the gaps; this commit closes them. No code changes - docs only. viin_brain/CHANGELOG.md - New "## [Unreleased]" section (Keep-a-Changelog; PR not merged so no version fabricated - manifest stays 0.3). Added/Changed/Fixed covering: native web_editor Wysiwyg in the quick-create drawer (ADR-003 Path B), responsive 3-pane layout, a11y landmarks, sidebar lists linked notes (mention + record-link union), friendly "Link a document" picker, theme token cycle repair, resize-reactive layout, this-binding crash fixes, share-dialog/template-dialog fixes, P12 badge removal. viin_brain/README.md - "What's new - UI/UX polish wave (unreleased)" subsection; responsive note in the OWL client-action description; ChatterAware sidebar bullet expanded (resize-reactive + linked-notes union); record-mention section notes explicit record links surface in the form sidebar; one sentence on the runtime --bs-* shim mechanism under Theme Compatibility. viin_brain/docs/brain/contenteditable_policy.md - Reconciled internal inconsistencies introduced when the drawer Path-B note was added earlier in this PR: bumped "Last verified at" to SHA e45c3a4 / 2026-06-02 (and re-confirmed the section-5 rogue grep is empty), added a dated section-8 status bullet recording the first Path-B surface, and reordered the section-7 change history to newest-last. viin_web_editor/CHANGELOG.md - New "## [Unreleased]" section: Removed the spike powerbox demo commands (viin_spike_hello / viin_spike_block + brain category); Changed the embedded_component_host docstring to point at native_commands.js (T-5). Known pre-existing nit (out of scope, anchors still resolve): the section-3 audit table line numbers in contenteditable_policy.md have drifted from the source; the durable grep anchors remain valid. Killed Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [TEST] viin_brain: JS smoke + behaviour tests for the UI/UX polish wave The wave reworked the form-sidebar quick-create flow heavily but left the JS surfaces unguarded. Adds protection at two altitudes (ETHOS §11 - guard the behaviour, not the click): 1. Quick-create drawer tour (brain_form_sidebar_drawer.js + Python runner): open the drawer, click a vault item and an entry-kind button (both crashed pre-fix via the bare-arrow "this" loss), assert the native web_editor Wysiwyg mounts, type a rich body, Save & Close. The runner then asserts the server outcome - page created, title + rich body persisted, entry_kind applied (so the crash fix produced an *effect*, not just "did not throw"), and the note surfaces on the record via the sidebar union (#1). Verified on a clean DB: 12/12 steps + all assertions green. 2. BrainFormSidebar layout unit tests (brain_form_sidebar_layout_tests.js): panel/rail/strip mapping + the resize-reactive guard - mutating the viewport size after mount must re-render the layout. This fails if the useState(useService('ui')) wrap regresses to a bare useService (the #3 bug). All assertions pass (verified in isolation: 4/4). Registers both in __manifest__.py (web.assets_tests / web.qunit_suite_tests) and the runner in tests/__init__.py. Killed Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [TEST] viin_brain: JS smoke + behaviour tests for the UI/UX polish wave The wave reworked the form-sidebar quick-create flow heavily but left the JS surfaces unguarded. Adds protection at two altitudes (ETHOS §11 - guard the behaviour, not the click): 1. Quick-create drawer tour (brain_form_sidebar_drawer.js + Python runner): open the drawer, click a vault item and an entry-kind button (both crashed pre-fix via the bare-arrow "this" loss), assert the native web_editor Wysiwyg mounts, type a rich body, Save & Close. The runner then asserts the server outcome - page created, title + rich body persisted, entry_kind applied (so the crash fix produced an *effect*, not just "did not throw"), and the note surfaces on the record via the sidebar union (#1). Verified on a clean DB: 12/12 steps + all assertions green. 2. BrainFormSidebar layout unit tests (brain_form_sidebar_layout_tests.js): panel/rail/strip mapping + the resize-reactive guard - mutating the viewport size after mount must re-render the layout. This fails if the useState(useService('ui')) wrap regresses to a bare useService (the #3 bug). All assertions pass (verified in isolation: 4/4). Registers both in __manifest__.py (web.assets_tests / web.qunit_suite_tests) and the runner in tests/__init__.py. Failed
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [TEST] viin_brain: JS smoke + behaviour tests for the UI/UX polish wave The wave reworked the form-sidebar quick-create flow heavily but left the JS surfaces unguarded. Adds protection at two altitudes (ETHOS §11 — guard the behaviour, not the click): 1. Quick-create drawer tour (brain_form_sidebar_drawer.js + Python runner): open the drawer, click a vault item and an entry-kind button (both crashed pre-fix via the bare-arrow loss), assert the native web_editor Wysiwyg mounts, type a rich body, Save & Close. The runner then asserts the server outcome — page created, title + rich body persisted, entry_kind applied (so the crash fix produced an *effect*, not just "did not throw"), and the note surfaces on the record via the sidebar union (#1). Verified on a clean DB: 12/12 steps + all assertions green. 2. BrainFormSidebar layout unit tests (brain_form_sidebar_layout_tests.js): panel/rail/strip mapping + the resize-reactive guard — mutating the viewport size after mount must re-render the layout. This fails if the useState(useService('ui')) wrap regresses to a bare useService (the #3 bug). All assertions pass (verified in isolation: 4/4). Registers both in __manifest__.py (web.assets_tests / web.qunit_suite_tests) and the runner in tests/__init__.py. Killed Not started Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [IMP] viin_brain: drawer note body uses native web_editor Wysiwyg (ADR-003 Path B) Replaces the quick-create drawer's hand-rolled contenteditable + execCommand toolbar with the native web_editor Wysiwyg (OdooEditor) — the editor the project's own ADR-003 mandates (TipTap rejected; PageEditor migration is P16). Why the previous approach was wrong: - It declared a raw, static contenteditable="true" in Brain markup, which is a rogue editable surface under the normative contenteditable_policy.md (two-surface invariant §2). The §5 rogue-detection lint went non-empty. Why native Wysiwyg fits here (and PageEditor does not): - Wysiwyg edits in-memory HTML and exposes getValue(), so it needs no persisted page — the drawer's create-on-save / discard flow is preserved. PageEditor requires an existing pageId and brings full-pane chrome + autosave/collab machinery, unsuitable for a quick-capture drawer. - viin_web_editor already patches Wysiwyg._getPowerboxOptions, so the Brain powerbox/slash commands appear automatically (parity, no extra code). Implementation (OSM-grounded, mirrors web_editor HtmlField + the in-org viin_customizer HTMLEditor pattern): - Lazy-load web_editor.backend_assets_wysiwyg in onWillStart, mount Wysiwyg as a dynamic <t t-component>; capture the instance via the startWysiwyg prop. - Build the Wysiwyg props ONCE (stable identity) so the dynamic component does not remount and drop the captured instance. - _bodyHtml() reads getValue(); empty sentinel <p><br></p> => no content. - The editable's contenteditable lives inside web_editor (audited), so Brain declares none: contenteditable_policy.md §5 grep is empty again. Policy §2 + change history updated to document the delegated surface. Verified live (sale.order form, pg16): drawer mounts exactly one Wysiwyg, rich content persists (content_html=<p><b>…</b></p>), §5 lint empty, module upgrade compiles assets clean. Failed
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [FIX] viin_brain: sidebar lists linked notes + rich-text drawer + resize-reactive layout Three review findings on the form-view Brain sidebar (sale.order): 1. A note created via the quick-create drawer links the record on the Brain side but never appeared on the record's own sidebar/stat count. Root cause: the sidebar query (_brain_mentioning_page_ids) and the stat compute (_compute_brain_page_count) only looked at record_mention links, while the drawer writes an explicit viin.brain.page.record.link (linked_record_ids) — two distinct mechanisms. Union both so a note 'about this record' shows on the record. Page-level ACL preserved (count filtered through an ACL-applied page search; list filtered by the downstream page read). Behaviour tests added (fail pre-fix). 2. Drawer body was a plain textarea, so notes could not be formatted. Replaced with a contenteditable rich-text region + a static toolbar (bold / italic / heading / bulleted list) emitting semantic tags; innerHTML is saved as content_html (mirrors PageEditor). 3. The form sidebar layout (panel/rail/strip) did not react to browser resize. Root cause: uiService was read via bare useService('ui'), so reads of ui.size did not subscribe the component. Wrapped with useState (canonical Odoo 17 pattern) so layoutMode recomputes on resize. Failed
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [FIX] viin_brain: form-sidebar drawer crashes + inline note body + header padding Review of the Brain form sidebar (sale.order / res.partner) surfaced three issues: 1. Create-drawer crashes (same unbound-`this` class as the share-dialog fix): `t-on-click="() => onVaultSelect(vault.id)"` / `() => onEntryKindSelect(...)` call a BARE method inside an inline arrow, so OWL invokes it without the component as `this` → "Cannot read properties of undefined (reading 'state')" when picking a vault or an entry kind. Swept the whole class: every `=> onXxx(` arrow handler now uses `=> this.onXxx(` (9 sites across brain_create_drawer, brain_form_sidebar [onOpenPage — would crash on a linked page click], moderation_panel, database_calendar_view drag/drop). 2. Header "too close to the left": the form-sidebar header rendered with NO padding. Root cause — `padding: … calc(map-get($spacers, 2) * 0.75)`: SCSS does NOT evaluate `map-get()` inside a CSS `calc()`, so it compiled to the literal `calc(map-get($spacers, 2) * 0.75)` — invalid, which makes the browser drop the ENTIRE padding declaration → 0 padding. Same class as the earlier LibSass `min()` regression. Fixed by letting Sass compute the value directly: `(map-get($spacers, 2) * 0.75)` → `padding: 8px 16px 6px`. 3. Note body not editable inline (had to leave the form for the Brain app — a UX failure when the note must reference the open record): the create drawer now has an inline body textarea; its content is saved as `content_html` on creation (the page model syncs content_plain/blocks), so the user writes the note while the record stays on screen. "Save & Open in Brain" still available for rich editing. Verified live (res.partner form): vault + entry-kind selection no longer crash; note created with body content + entry_kind + record link; header padding renders. Killed Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [FIX] viin_brain: share dialog crash/no-link + friendly link-to-record picker Two UX/runtime issues found in review: 1. Share dialog (share_dialog): - Toggling "Public link" threw "Cannot read properties of undefined (reading 'state')" — the CheckBox onChange was wired as `onChange="(val) => onToggleLink(val)"`, an unbound inline arrow that OWL invoked without the component as `this`. Use `onChange.bind` so the handler keeps its component binding. - "Save settings" created the link but the public URL never appeared: the URL block is gated on `state.enabled`, which onSave never set. Saving a share link now sets `state.enabled = true` so the URL + Copy show. 2. Link-to-record (link_record_dialog): replaced the raw "model:id" text box (e.g. "res.partner:42" — meaningless to end users) with Odoo's native friendly pickers — ModelSelector ("Document type", model display names) + RecordSelector ("Document", record-name autocomplete). Optional admin whitelist via _brain_default_linkable_models; empty = all models (Odoo reference-field default). Output contract unchanged (onConfirm "model:id"). Handlers passed with `.bind` to avoid the same unbound-`this` class of bug. Verified in a live Odoo 17 instance: share toggle no longer crashes and the URL shows after save; the link dialog renders the model→record friendly chain; the Brain form-sidebar (Brain Pages stat + Brain Note + BRAIN rail) renders correctly on chatter forms (res.partner) — issue #3 was blocked by #2, not a bug. Killed Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [IMP] viin_brain: filter pills compact single row (mockup parity) + note SSOT dup Killed Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [FIX] viin_brain: repair theme token system — Brain UI was theme-blind (off-theme) Root cause (CEO-reported "rất xấu, không khớp theme Odoo"; PRE-EXISTING, not the polish wave): theme_tokens.scss Block 1 shimmed Bootstrap-5.3 CSS custom props with a SELF-REFERENTIAL pattern `--bs-x: var(--bs-x, fallback)`. A custom property referencing itself is a CSS dependency cycle → computes to the guaranteed-invalid (empty) value; the fallback is never reached. Odoo 17 ships Bootstrap 5.1.3 and emits NONE of --bs-* at runtime (verified empty at :root on the live instance), so every --o-brain-* token that chained into --bs-* (page / sidebar / context backgrounds, borders, muted text, badge text, trust + AI surfaces) resolved to empty → panes rendered transparent with no borders, text fell back to gray, badges lost their fill. The whole app looked flat/off-theme. Fix: rewrite Block 1 as PLAIN (non-self-referential) assignments anchored to Odoo-17 runtime vars that DO carry values (--gray-100..900, --primary, --secondary, --success/-warning/-danger/-info, --body-*), each with a literal fallback. This single change resolves the entire downstream token chain (Block 2 surfaces/borders/text, Block 3 trust/AI) — verified in-browser: --o-brain-bg-sidebar #f8f9fa, --o-brain-border #dee2e6, badge text #fff, header white + 1px borders. Odoo consumes no runtime --bs-*, so global :root definition is side-effect-free; a themed deploy that emits real --bs-* overrides by cascade. Also: vault badge reverted to brand accent fill with white text (earlier wave darkening treated the wrong cause — gray text came from the empty --bs-white, not the fill); badge radius 6px→4px and page title 2.1rem→2rem (mockup/spec). Killed Not finished
merged [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive [IMP] viin_brain,viin_web_editor: Brain core UI polish wave — runtime bug + a11y/responsive Khảo sát UI live cụm Brain core phát hiện loạt defect runtime + a11y/responsive mà tour test chưa phủ. Wave này đun cạn nhóm bug + polish (low-risk, không chạm kiến trúc; tách khỏi P16 OdooEditor / P14 CRDT còn gated). Runtime bug: - C-1: dialog "+ New page" body rỗng — web.Dialog (Odoo 17) render body qua DEFAULT slot, không phải named "body" slot; bỏ <t t-set-slot="body"> (khớp MovePageDialog). Reproduce bằng click thật + thêm tour brain_new_page_tour. - C-2: context-pane tab bar overflow ở desktop — 4/6 tab bị giấu; flex-wrap để cả 6 tab truy cập được (root cause hiệu lực ở _misc.scss override, fix cả 2). a11y: - H-1: bỏ badge dev "P12" lộ ra UI; nhãn tab trung tính. - H-3: contrast badge vault initials 2.33:1 -> 5.09:1 (token color-mix riêng). - H-4: thêm landmark <main> bọc center pane (Lighthouse landmark-one-main). - M-2/M-3/M-4/M-7: badge class nhất quán, aria-disabled tab, aria-label title động. responsive (H-2): - _responsive.scss: <768px single-pane + sidebar drawer (hamburger header toggle), 768-1024px 2-pane, title ellipsis; escape LibSass min() (mixed px/vw). khác: - M-1: đồng bộ nhãn sidebar khi rename page (env.bus brain-page-renamed). - M-6/L-1/L-2/L-5: graph filter wrap, icon timeline calendar, collapsed sidebar clip, dedupe message daily-note. - T-5: gỡ powerbox spike demo command (viin_web_editor) + scrub comment chết. Verified: cài sạch trên Odoo 17 community + pg16; web.assets_backend compile sạch; UI re-review 9/9 PASS; Lighthouse a11y 76 -> 77. Killed Not finished
merged [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) [FIX] viin_ai_agent: stub contract test robust to framework-injected x_ fields The post_install tests test_schedule_stub_has_no_rogue_fields and test_approval_chain_stub_has_no_rogue_fields failed on runbot because viin_workflow_automation grafts x_workflow_* computed fields onto base (every concrete model) in the full Viindoo suite. Those framework-owned fields are outside the P-OPS-1 stub contract, which governs only viin_ai-declared fields. Exclude x_-prefixed fields from the comparison; viin_ai never declares x_-prefixed fields, so the contract's protection is preserved. Killed
merged [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) [IMP] viin_ai_ops,viin_ai_agent,viin_ai_chat: address PR #36 review findings Fixes from OSM-grounded (Viindoo 17.0) code review of this PR. viin_ai_ops (HIGH): - control_policy._match_proposal: scope policy search by the proposal's company_id ('|' company_id=False / =proposal.company) — ir.rule only scopes to the *current user's* allowed companies, not the proposal's, so explicit filtering is mandatory for multi-company isolation. Drop the redundant ('active','=',True) term (auto-filtered by _active_name). - goal / work_item / agent_ext / trace_ext compute counts: replace search_count() inside a for-loop (N+1) with a single read_group(); add @api.depends('work_item_ids' / 'proposal_ids') where a reverse O2M exists (goal, work_item). agent_ext/trace_ext have no reverse O2M yet → empty @api.depends() documented, deferred to P-OPS-2. viin_ai_ops (MED): - action_proposal.action_route: gate behind base.group_system so a regular internal user (perm_write) cannot self-route a proposal to 'approved' via a low-risk auto_execute policy. Flow-lock unchanged. Documented assumption: swap for a dedicated approval group in P-OPS-2. - control_policy.model_id: documented as not-yet-matched (proposal has no res_model field this phase) with an explicit P-OPS-2 TODO instead of a silent no-op. - trace form: insert the stat button_box before the first <sheet> group (was position='inside' → rendered at sheet bottom); rename the div to the canonical name='button_box'. viin_ai_agent (HIGH/MED): - test_stub_deprecation_contract: replace dead assertIsNotNone(env[...]) (env[] raises KeyError before the assert → ERROR not FAIL) with assertIn(name, env.registry.models); add rogue-field contract tests so the suite turns RED if a deprecated stub ever accumulates a field. - stubs.py: docstring 'AbstractModels' → 'concrete Models'. - pre-migrate 17.0.0.2.1: document the idempotency contract for future SQL (no behaviour change). viin_ai_chat (LOW): - wrap the NONCE_MISMATCH content string in _() for i18n parity with the NO_PENDING branch. Failed
merged [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) [FIX] viin_ai_chat: absent pending tool call → NO_PENDING, not a server warning Root cause (not the symptom): /viin_ai/chat/tool_confirm raised UserError('No pending tool call: %s') whenever _load_pending_tool returned None. But an absent pending state is a NORMAL, designed-in client condition — the proposal was already handled, the single-use nonce was burned, the session-stored state expired, or the user double-submitted the dialog. Raising UserError from a JSON route makes Odoo log it at WARNING on the odoo.http logger, so this fired for ordinary user behaviour in production (and surfaced on runbot as 'Subbuild #...: No pending tool call: wi15-nonce-burn-001'). The sibling NONCE_MISMATCH condition already returns a structured, FE-renderable error instead of raising. Make the no-pending branch do the same: return {status:'error', error_code:'NO_PENDING'}. The agent loop is never entered, nothing is logged, the FE renders a friendly recovery message via its existing status==='error' branch. This supersedes the earlier change that merely @mute_logger'd the AC10 test — that hid the test warning while production logs kept filling. Tests now assert the corrected contract (intent, not output): - AC06: unknown/expired id → NO_PENDING + tool never executed - AC10: post-burn retry → NO_PENDING (was: tolerate UserError) docs/ai/security.md documents the NO_PENDING contract beside NONCE_MISMATCH. Killed Not finished
merged [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) [FIX] viin_ai_chat: mute odoo.http in AC10 nonce-burn test test_AC10_nonce_burned_after_mismatch intentionally triggers the 'No pending tool call' UserError on its step-2 retry (the pending state was burned after the prior NONCE_MISMATCH). Odoo's JSON dispatcher logs that UserError at WARNING on the odoo.http logger, which runbot surfaces as a build warning (Subbuild #384049: 'No pending tool call: wi15-nonce-burn-001'). The expected-error sibling tests in this class (AC01/02/03/06) already carry @mute_logger('odoo.http', ...); AC10 was added in M1 W5 without it. Apply the same decorator so the deliberately provoked warning no longer pollutes the log. No behavior or assertion change. Killed Not finished
merged [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) New Community-compatible module sitting between the AI capability layer (viin_ai_agent) and external approval/workflow systems. Provides four operating objects and the side-effect flow lock from operating-model.md §3. Models: - viin.ai.goal: typed operational goal (KPI / initiative / routine / risk_control) - viin.ai.work.item: authoritative task state (draft -> in_progress -> done/cancelled) - viin.ai.action.proposal: a side-effecting action awaiting governance routing - viin.ai.control.policy: sequence-ordered rule routing proposals to auto_execute / require_approval / block Side-effect flow lock: medium/high risk proposals can never auto-execute; action_route() forces them to 'pending' regardless of policy, and an unmatched proposal defaults to 'pending' (safe default). Smart buttons: Work Items count on viin.ai.agent; Proposals count on viin.ai.trace. Multi-company ir.rule on all four models; control.policy is read-only for standard users. Also marks the viin.ai.schedule / viin.ai.approval.chain stub anchors in viin_ai_agent as deprecated (module + class docstrings, removal checklist, "deprecated" in _description) and adds a non-destructive migration scaffold. Deprecation is documentation-only by design: no per-registry-load warning, which would flood server/CI logs on every startup. FK anchors are kept intact until the relations are migrated in a later phase. Tests: 46 (viin_ai_ops) + stub-deprecation contract; full suite green (0 failed, 0 error of 104 across viin_ai_ops + viin_ai_agent). Killed
merged [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) [ADD] viin_ai_ops: Operating Layer foundation (P-OPS-1) New Community-compatible module sitting between the AI capability layer (viin_ai_agent) and external approval/workflow systems. Provides four operating objects and the side-effect flow lock from operating-model.md §3. Models: - viin.ai.goal: typed operational goal (KPI / initiative / routine / risk_control) - viin.ai.work.item: authoritative task state (draft -> in_progress -> done/cancelled) - viin.ai.action.proposal: a side-effecting action awaiting governance routing - viin.ai.control.policy: sequence-ordered rule routing proposals to auto_execute / require_approval / block Side-effect flow lock: medium/high risk proposals can never auto-execute; action_route() forces them to 'pending' regardless of policy, and an unmatched proposal defaults to 'pending' (safe default). Smart buttons: Work Items count on viin.ai.agent; Proposals count on viin.ai.trace. Multi-company ir.rule on all four models; control.policy is read-only for standard users. Also marks the viin.ai.schedule / viin.ai.approval.chain stub anchors in viin_ai_agent as deprecated (docstring + registry warning) and adds a non-destructive migration scaffold. FK anchors are intentionally kept intact until the relations are migrated in a later phase. Tests: 46 (viin_ai_ops) + stub-deprecation contract; full suite green. Failed
merged [DOC] docs,ci: close M1 — SQL template ACL ✅ + Runbot-only CI model + local pre-commit hook [REM] docs: purge viinForge residue — deprecated product viinForge (orchestration platform/wrapper) đã bị loại bỏ. Gỡ toàn bộ 13 reference trên 9 file: - CONTRIBUTING.md: thay wrapper ~/viinforge/ops/scripts/run_{tests,odoo}.sh + venv rỗng viindoo17 bằng lệnh odoo-bin native (venv odoo17 + addons-path deps) — giải luôn gốc 'không biết cách chạy test'. - conventions.md, brain/architecture.md, brain/ui-design.md: gỡ con trỏ tới viinForge workspace/knowledge/reuse-map, giữ quy tắc cứng inline. - decisions/wi-001, wi-003, history/release-notes-v1.3, contenteditable_policy: gỡ con trỏ audit/pattern/worktree tới viinforge repo (dead pointer), giữ nội dung coherent. - brain/mockups/v3/ui_design_v3.html: gỡ checklist item 'viinForge Monitor TUI' (demo data). Verify: grep -rIni viinforge + git grep = 0 match. Append-only decision records chỉ gỡ dead pointer, không sửa nội dung quyết định. Killed
merged [DOC] docs,ci: close M1 — SQL template ACL ✅ + Runbot-only CI model + local pre-commit hook [DOC] docs,ci: close M1 — SQL template ACL ✅ + Runbot-only CI model + local pre-commit hook Phase 3.7 carry-over / Gate M1 closure: - roadmap §5 + security.md checklist: tick SQL template ACL (evidence: sql_template.py _check_sql_safety + ACL CTE __acl_allowed + group_ids allowlist + JSON Schema params; test_sql_template.py ma trận ACL/multi-company all pass). - roadmap §6: định nghĩa CI gate = Viindoo Runbot 'CI/runbot' (Runbot-only theo chuẩn org, tvtmaaddons cũng zero GitHub Actions); gỡ giả định P15 tour gate khóa bởi Chrome runner GitHub Actions — tour chạy trên Runbot. Đồng bộ M1 condition + Track B refs + REPO_LAYOUT. - tooling: thêm husky + mở rộng lint-staged (ruff check/format cho *.py) làm lớp verify local; CONTRIBUTING hướng dẫn chạy test qua odoo-bin --test-tags (không phải pytest thuần). Why: PR #34 chủ động drop CI repo-specific để dùng Runbot external; docs cần khớp thực tế để M1 đóng sạch và dev có lớp verify nhanh trước push. Killed Not finished
merged M1 Wave 1: 3 security HIGH gap closure (trace ir.rule + nl_query sanitize + confirmation nonce) [REMOVE] ci: drop all repo-specific CI — workflows + scripts (PR #34) Stop maintaining ai17's own GitHub Actions CI; rely on the external Viindoo Runbot check instead. Deleted: .github/workflows/{brain-tours,ci,preflight-pgvector}.yml; ops/ci/* (5 files); tools/brain_v3_ci_check.py; docs/brain/ci-tour-gate.md (runbook for the removed gate). Docs cleaned (no history/changelog touched): README.md drops CI Tour Gate link; pgvector_install.md replaces check_pgvector.sh with inline psql, removes the CI section, renumbers §6-§8; tools/README.md drops the brain_v3_ci_check.py section + pre-push runbook. Part of Wave 1 — PR #34. Killed