From 051c9f0c221b462ea6a35fbb159ec016f6c61420 Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 16 Jun 2026 01:15:06 +0200 Subject: [PATCH] feat(ui): constrain add-account to a root-type dropdown + sub-path Free-typing the full hierarchical name let admins fat-finger the parent (wrong/invalid root). Replace the single name field with a required Account Type select (the 5 valid roots, mirroring _VALID_ACCOUNT_PREFIXES) plus a sub-account input, a live 'Will create: ...' preview, and per-segment validation (each part must be a capitalized Beancount account component). The root prefix is now structurally guaranteed valid. Co-Authored-By: Claude Opus 4.8 (1M context) --- static/js/index.js | 32 ++++++++++++++++++++++++++------ templates/libra/index.html | 23 ++++++++++++++++++----- 2 files changed, 44 insertions(+), 11 deletions(-) diff --git a/static/js/index.js b/static/js/index.js index a10d50c..2b4c750 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -71,7 +71,8 @@ window.app = Vue.createApp({ }, addAccountDialog: { show: false, - name: '', + rootType: 'Expenses', + subPath: '', description: '', loading: false }, @@ -292,6 +293,16 @@ window.app = Vue.createApp({ }) return options }, + accountRootTypes() { + // The five Beancount root account types — the only valid parents. + // Mirrors the server's _VALID_ACCOUNT_PREFIXES. + return ['Assets', 'Liabilities', 'Equity', 'Income', 'Expenses'] + }, + addAccountFullName() { + const sub = (this.addAccountDialog.subPath || '').trim().replace(/^:+|:+$/g, '') + if (!this.addAccountDialog.rootType || !sub) return '' + return `${this.addAccountDialog.rootType}:${sub}` + }, userOptions() { const options = [] this.users.forEach(user => { @@ -573,17 +584,26 @@ window.app = Vue.createApp({ } }, showAddAccountDialog() { - this.addAccountDialog.name = '' + this.addAccountDialog.rootType = 'Expenses' + this.addAccountDialog.subPath = '' this.addAccountDialog.description = '' this.addAccountDialog.show = true }, async submitAddAccount() { - const name = (this.addAccountDialog.name || '').trim() - const validPrefixes = ['Assets:', 'Liabilities:', 'Equity:', 'Income:', 'Expenses:'] - if (!validPrefixes.some(p => name.startsWith(p))) { + const name = this.addAccountFullName + if (!name) { + this.$q.notify({type: 'warning', message: 'Enter a sub-account name'}) + return + } + // Each segment under the root must be a valid Beancount account + // component: start with an uppercase letter, then letters/digits/hyphens. + const badSegment = name.split(':').slice(1).find( + seg => !/^[A-Z][A-Za-z0-9-]*$/.test(seg) + ) + if (badSegment !== undefined) { this.$q.notify({ type: 'warning', - message: `Account name must start with one of: ${validPrefixes.join(', ')}` + message: `Invalid segment "${badSegment}" — each part must start with a capital letter (letters, digits, hyphens only)` }) return } diff --git a/templates/libra/index.html b/templates/libra/index.html index f36c466..5369f72 100644 --- a/templates/libra/index.html +++ b/templates/libra/index.html @@ -1251,15 +1251,28 @@
Add Account
+ + +
+ Will create: {% raw %}{{ addAccountFullName }}{% endraw %} +
+ Create Account