@aryacode/dsh-aryacode-polish 0.2.4 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,92 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.5] - 2026-10-27
4
+
5
+ ### Fixed
6
+
7
+ - **The card's own page described an older plugin.** The README that ships in the
8
+ tarball *is* the page body in the Plugins market, and it still said "0.2.0, one
9
+ feature shipped" and listed a layout directory that does not exist. Status,
10
+ layout, and requirements now match the package.
11
+
12
+ ### Changed
13
+
14
+ - `HANDOFF.md` was rewritten for the shipped bundle: the contracts that cost time
15
+ (a row's config namespace is the row id, not the slot key; no bundler for the
16
+ face; the client session contract exposes no events), the slot geography around
17
+ the account row, the development loop, and the release order that keeps the
18
+ catalogue record. `test/docs.spec.mjs` now fails if the briefing drifts back to
19
+ the stale claims it once made.
20
+
21
+ ## [0.3.4] - 2026-10-27
22
+
23
+ ### Added
24
+
25
+ - **The bundle says which Harness it runs on.** `dsh.engines.dsh` is now declared
26
+ (`^0.2.1-rc.3`, the revision the two surfaces were verified against), so the
27
+ market can read the requirement instead of labelling the card "Host requirement
28
+ unknown", and an install onto an older Host can be refused rather than failing
29
+ at mount time. `test/manifests.spec.mjs` keeps the field present and semver.
30
+
31
+ ## [0.3.3] - 2026-10-27
32
+
33
+ ### Fixed
34
+
35
+ - **The balance read could wait forever.** When the Host withheld the
36
+ `account/watch` grant, the read's promise never settled: the callback never
37
+ ran, so nothing resolved and the line silently stayed empty for the life of the
38
+ page. The read now carries a timeout and also starts its call before the
39
+ effect's yielded disposer, so an effect runner that stops at the first yield
40
+ still performs it. `test/account.spec.mjs` pins all seven refusal paths.
41
+
42
+ ### Changed
43
+
44
+ - The read itself moved into `client/account.js`, beside `client/money.js`, for
45
+ the same reason: the build inlines it into the browser bundle and the spec
46
+ imports the same source.
47
+
48
+ ## [0.3.2] - 2026-10-27
49
+
50
+ ### Changed
51
+
52
+ - **The balance decisions are testable.** Which wallet a line shows, and how the
53
+ amount is rendered (currency symbol, two decimals, unparseable values shown
54
+ verbatim), moved into `client/money.js`. The build inlines that file into
55
+ `lib/client.js`, so `test/money.spec.mjs` covers the exact source the browser
56
+ runs instead of a copy.
57
+
58
+ ## [0.3.1] - 2026-10-27
59
+
60
+ ### Fixed
61
+
62
+ - **The balance line had no observable mount point.** When the account exposed no
63
+ wallet it rendered `null`, which is correct for the user but indistinguishable
64
+ from "the surface was never registered" for anyone verifying the build. The
65
+ component now always renders its wrapper (`data-polish-balance="empty"`, hidden)
66
+ and fills it with the amount only when the Host returns one.
67
+
68
+ ## [0.3.0] - 2026-10-27
69
+
70
+ ### Added
71
+
72
+ - **Remaining account balance in the sidebar foot.** A line above the account row
73
+ shows the account's own wallet (`$17.33`), read once through a
74
+ `remoteAccess.whenAccess('account/watch', …)` grant so the call is allowed and
75
+ the grant is released as soon as the read settles. It renders nothing at all
76
+ when the switch is off, when the Host exposes no wallet, or when the read is
77
+ refused — an empty line beats a number the Host did not give us.
78
+ - **A second switch, `accountBalance`.** It is declared once in
79
+ `entries/registry.js` and once in `client/switches.json`, exactly like the cost
80
+ switch, so the Configure page grows it automatically.
81
+
82
+ ### Notes
83
+
84
+ - The line sits in `sidebar.footer.action`, the one documented seat in the
85
+ sidebar foot. It renders *above* the account row, because
86
+ `SidebarRoot.tsx:405-411` puts `footerActions` before `settingsArea`; the only
87
+ seat below the login would be `settings.launcher`, which means shadowing the
88
+ account row and its whole menu.
89
+
3
90
  ## [0.2.4] - 2026-10-27
4
91
 
5
92
  ### Changed
package/README.md CHANGED
@@ -7,7 +7,8 @@ installed into and patches nothing. It is one package, one Plugins row, and one
7
7
  switch per feature, so a refinement that turns out to be wrong for a machine can
8
8
  be turned off without uninstalling anything.
9
9
 
10
- Status: **0.2.0.** One feature shipped: the estimated session cost pill.
10
+ Status: **0.3.4.** Two features shipped and released: the estimated session cost
11
+ pill and the account balance line.
11
12
 
12
13
  ## What it is
13
14
 
@@ -30,6 +31,7 @@ Configure page automatically.
30
31
  | Switch | Default | What it does |
31
32
  | --- | --- | --- |
32
33
  | **Estimated session cost** (`sessionCost`) | On | A `≈$0.39` pill in the composer dock that opens a breakdown card — cache hit, uncached input, cached input, output, requests, and the estimate — priced from the session's recorded usage at the published DeepSeek rates. |
34
+ | **Account balance** (`accountBalance`) | On | The account's remaining wallet (`$17.33`) in the sidebar foot, above the account row. Renders nothing when the Host exposes no wallet, so it never shows a number nobody gave it. |
33
35
 
34
36
  ### How the cost is computed
35
37
 
@@ -91,6 +93,8 @@ node --import tsx/esm apps/cli/src/bin.ts plugin --profile web add <path-to-tgz>
91
93
 
92
94
  ## Requirements
93
95
 
96
+ - AryaCode `^0.2.1-rc.3` (declared as `dsh.engines.dsh`; the revision both
97
+ surfaces were verified against).
94
98
  - AryaCode with the Cordis plugin API (`@deepseek-ai/cordis ~4.0.4`).
95
99
  - Node `^22.19.0 || >=24.0.0` to build from source.
96
100
 
@@ -113,10 +117,11 @@ entries/polish.js the row: Config derived from the registry, apply()
113
117
  entries/registry.js the feature list — the one place a feature is declared
114
118
  entries/usage.js the fold: model-attributed usage out of the session log
115
119
  entries/cost.js the polishCost projection: rates, pricing, host mount
116
- entries/features/ one module per feature
117
120
  entries/config.js volatile-reference accessors (resolve/flag/list/text)
118
121
  lib/index.js mount point re-exporting the row
119
122
  client/face.js browser face source: the Configure page and every surface
123
+ client/money.js which wallet is shown, and how the amount is rendered
124
+ client/account.js the granted, timeout-bounded balance read
120
125
  client/switches.json the browser half of the seam — one entry per switch
121
126
  scripts/build-client.mjs wraps client/face.js into lib/client.js
122
127
  lib/client.js generated: the bundle the Shell loads
@@ -0,0 +1,96 @@
1
+ /**
2
+ * Reading the account balance from a plugin face.
3
+ *
4
+ * The account Remote methods are gated, so the read runs inside a
5
+ * `remoteAccess.whenAccess('account/watch', …)` grant and the grant is released
6
+ * as soon as the read settles. Every failure — no service in this Shell, a
7
+ * refused grant, a failed call — resolves to `undefined` instead of throwing: a
8
+ * sidebar line is never worth breaking the shell over, and a line that stays
9
+ * empty is honest about a number it never received.
10
+ *
11
+ * This module is inlined into `lib/client.js` by `scripts/build-client.mjs` and
12
+ * imported directly by `test/account.spec.mjs`, so the tested source is the one
13
+ * the browser runs.
14
+ */
15
+
16
+ /**
17
+ * Build the account request identity for one balance read.
18
+ *
19
+ * The shipped account bundle inlines `DSH_CLIENT_VERSION` at build time; a plugin
20
+ * bundle has no such define, so the build stamps this package's own version and
21
+ * prefers the shell's value when one is inlined.
22
+ *
23
+ * @param version - the version this build stamps.
24
+ * @returns the metadata the account Remote methods carry.
25
+ */
26
+ export function clientMetadata(version) {
27
+ const stamped = typeof process !== 'undefined' && process.env !== undefined ? process.env.DSH_CLIENT_VERSION : undefined
28
+ return {
29
+ version: typeof stamped === 'string' && stamped !== '' ? stamped : version,
30
+ locale: typeof navigator !== 'undefined' && typeof navigator.language === 'string' && navigator.language !== ''
31
+ ? navigator.language
32
+ : 'en',
33
+ timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60,
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Build the one-shot balance reader.
39
+ *
40
+ * @param ctx - the browser plugin context; `ctx.get` resolves an optional service.
41
+ * @param version - the version this build stamps into the account metadata.
42
+ * @returns a reader that resolves to the balance view, or undefined when unavailable.
43
+ */
44
+ export function makeBalanceReader(ctx, version, { timeoutMs = 10000 } = {}) {
45
+ const service = name => {
46
+ try {
47
+ return ctx.get(name)
48
+ } catch {
49
+ return undefined
50
+ }
51
+ }
52
+ return async () => {
53
+ const access = service('remoteAccess')
54
+ const remote = service('remote')
55
+ if (access === undefined || remote === undefined) return undefined
56
+ const metadata = clientMetadata(version)
57
+ return await new Promise(resolve => {
58
+ let settled = false
59
+ let stop
60
+ const timer = setTimeout(() => finish(undefined), timeoutMs)
61
+ const finish = value => {
62
+ if (settled) return
63
+ settled = true
64
+ clearTimeout(timer)
65
+ resolve(value)
66
+ if (typeof stop === 'function') {
67
+ try {
68
+ stop()
69
+ } catch {
70
+ /* the grant may already have been released */
71
+ }
72
+ }
73
+ }
74
+ // The read starts before the yield so an effect runner that stops at the
75
+ // first yielded disposer still performs the call.
76
+ stop = access.whenAccess('account/watch', function* () {
77
+ void (async () => {
78
+ try {
79
+ const result = await remote.account.getBalance(metadata)
80
+ const value = result !== undefined && result.ok === true ? result.value : undefined
81
+ finish(value !== undefined && value.status === 'ready' ? value : undefined)
82
+ } catch {
83
+ finish(undefined)
84
+ }
85
+ })()
86
+ yield () => {
87
+ if (!settled) {
88
+ settled = true
89
+ clearTimeout(timer)
90
+ resolve(undefined)
91
+ }
92
+ }
93
+ })
94
+ })
95
+ }
96
+ }
package/client/face.js CHANGED
@@ -313,6 +313,60 @@ function CostPill(props) {
313
313
  : null)
314
314
  }
315
315
 
316
+ /**
317
+ * The remaining-balance line in the sidebar foot.
318
+ *
319
+ * It renders nothing at all when the switch is off, the account exposes no
320
+ * wallet, or the read was refused — an empty line is better than a line that
321
+ * claims a number the Host did not give us.
322
+ *
323
+ * @param props - the slot's `wide` flag, our lazy `configForms`, and the balance reader.
324
+ * @returns the balance line, or null.
325
+ */
326
+ function BalanceLine(props) {
327
+ const enabled = useSwitch(props.configForms, 'accountBalance', true)
328
+ const [wallet, setWallet] = React.useState(undefined)
329
+ const read = props.loadBalance
330
+ React.useEffect(() => {
331
+ if (!enabled || typeof read !== 'function') return undefined
332
+ let live = true
333
+ Promise.resolve()
334
+ .then(() => read())
335
+ .then(view => {
336
+ if (live) setWallet(walletOf(view))
337
+ }, () => {
338
+ if (live) setWallet(undefined)
339
+ })
340
+ return () => {
341
+ live = false
342
+ }
343
+ }, [enabled, read])
344
+ if (!enabled) return null
345
+ // The wrapper stays in the DOM even when there is no number to show, so a
346
+ // reader (and the verification script) can tell "registered but empty" from
347
+ // "not registered at all". An empty flex line costs the layout nothing.
348
+ if (wallet === undefined) {
349
+ return React.createElement('div', { 'data-polish-balance': 'empty', style: { display: 'none' } })
350
+ }
351
+ const money = formatBalance(wallet)
352
+ return React.createElement('div', {
353
+ title: 'Remaining account balance',
354
+ style: {
355
+ display: 'flex',
356
+ alignItems: 'center',
357
+ gap: '6px',
358
+ padding: '2px 8px 4px',
359
+ fontSize: '12px',
360
+ lineHeight: '16px',
361
+ minWidth: 0,
362
+ overflow: 'hidden',
363
+ whiteSpace: 'nowrap',
364
+ textOverflow: 'ellipsis',
365
+ opacity: 0.75,
366
+ },
367
+ }, props.wide === false ? money : 'Balance ' + money)
368
+ }
369
+
316
370
  return {
317
371
  inject: ['slots'],
318
372
  /**
@@ -338,5 +392,12 @@ return {
338
392
  order: 2,
339
393
  inject: () => ({ configForms }),
340
394
  }, CostPill))
395
+ const loadBalance = makeBalanceReader(ctx, CLIENT_VERSION)
396
+ ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({
397
+ name: 'sidebar.footer.action',
398
+ id: 'polish-balance',
399
+ order: 3,
400
+ inject: () => ({ configForms, loadBalance }),
401
+ }, BalanceLine))
341
402
  },
342
403
  }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * The two pure decisions behind the balance line: which wallet to show, and how
3
+ * to render it.
4
+ *
5
+ * They live in their own file because the browser face bundle is only ever run
6
+ * inside the Shell, where nothing can be imported and asserted — so the build
7
+ * inlines this module into `lib/client.js` (with the `export` keywords dropped)
8
+ * while `test/money.spec.mjs` imports it directly. What the test covers is
9
+ * therefore the same source the browser runs, not a copy of it.
10
+ */
11
+
12
+ /**
13
+ * Pick the wallet a balance line should show.
14
+ * @param view - the balance view the account Remote returned.
15
+ * @returns the first wallet carrying a balance, or undefined when the view has none.
16
+ */
17
+ export function walletOf(view) {
18
+ if (view === undefined || view === null || view.status !== 'ready' || !Array.isArray(view.value)) return undefined
19
+ return view.value.find(wallet => wallet !== null && typeof wallet === 'object' && typeof wallet.balance === 'string')
20
+ }
21
+
22
+ /**
23
+ * Format one wallet as money.
24
+ * @param wallet - a wallet with `currency` and `balance`.
25
+ * @returns the amount with its currency symbol, at two decimals.
26
+ */
27
+ export function formatBalance(wallet) {
28
+ const symbol = wallet.currency === 'CNY' ? '\u00A5' : '$'
29
+ const amount = Number(wallet.balance)
30
+ return Number.isFinite(amount) ? symbol + amount.toFixed(2) : symbol + String(wallet.balance)
31
+ }
@@ -6,6 +6,12 @@
6
6
  "label": "Estimated session cost",
7
7
  "description": "Show what the open session has cost so far, priced from recorded usage at the published DeepSeek rates.",
8
8
  "defaultOn": true
9
+ },
10
+ {
11
+ "key": "accountBalance",
12
+ "label": "Account balance",
13
+ "description": "Show the remaining account balance in the sidebar foot, above the account row.",
14
+ "defaultOn": true
9
15
  }
10
16
  ]
11
17
  }
@@ -40,6 +40,12 @@ export const features = [
40
40
  defaultOn: true,
41
41
  mount: mountCost,
42
42
  },
43
+ {
44
+ id: 'accountBalance',
45
+ label: 'Account balance',
46
+ description: 'Show the remaining account balance in the sidebar foot, above the account row.',
47
+ defaultOn: true,
48
+ },
43
49
  ]
44
50
 
45
51
  /**
package/lib/client.js CHANGED
@@ -13,10 +13,146 @@ window.__ModuleLoader__.load({
13
13
  "label": "Estimated session cost",
14
14
  "description": "Show what the open session has cost so far, priced from recorded usage at the published DeepSeek rates.",
15
15
  "defaultOn": true
16
+ },
17
+ {
18
+ "key": "accountBalance",
19
+ "label": "Account balance",
20
+ "description": "Show the remaining account balance in the sidebar foot, above the account row.",
21
+ "defaultOn": true
16
22
  }
17
23
  ]
18
24
  const ROW_ID = "polish"
19
25
  const ROW_KEY = "@aryacode/dsh-aryacode-polish" + '#' + ROW_ID
26
+ const CLIENT_VERSION = "0.3.5"
27
+
28
+ /**
29
+ * The two pure decisions behind the balance line: which wallet to show, and how
30
+ * to render it.
31
+ *
32
+ * They live in their own file because the browser face bundle is only ever run
33
+ * inside the Shell, where nothing can be imported and asserted — so the build
34
+ * inlines this module into `lib/client.js` (with the `export` keywords dropped)
35
+ * while `test/money.spec.mjs` imports it directly. What the test covers is
36
+ * therefore the same source the browser runs, not a copy of it.
37
+ */
38
+
39
+ /**
40
+ * Pick the wallet a balance line should show.
41
+ * @param view - the balance view the account Remote returned.
42
+ * @returns the first wallet carrying a balance, or undefined when the view has none.
43
+ */
44
+ function walletOf(view) {
45
+ if (view === undefined || view === null || view.status !== 'ready' || !Array.isArray(view.value)) return undefined
46
+ return view.value.find(wallet => wallet !== null && typeof wallet === 'object' && typeof wallet.balance === 'string')
47
+ }
48
+
49
+ /**
50
+ * Format one wallet as money.
51
+ * @param wallet - a wallet with `currency` and `balance`.
52
+ * @returns the amount with its currency symbol, at two decimals.
53
+ */
54
+ function formatBalance(wallet) {
55
+ const symbol = wallet.currency === 'CNY' ? '\u00A5' : '$'
56
+ const amount = Number(wallet.balance)
57
+ return Number.isFinite(amount) ? symbol + amount.toFixed(2) : symbol + String(wallet.balance)
58
+ }
59
+
60
+ /**
61
+ * Reading the account balance from a plugin face.
62
+ *
63
+ * The account Remote methods are gated, so the read runs inside a
64
+ * `remoteAccess.whenAccess('account/watch', …)` grant and the grant is released
65
+ * as soon as the read settles. Every failure — no service in this Shell, a
66
+ * refused grant, a failed call — resolves to `undefined` instead of throwing: a
67
+ * sidebar line is never worth breaking the shell over, and a line that stays
68
+ * empty is honest about a number it never received.
69
+ *
70
+ * This module is inlined into `lib/client.js` by `scripts/build-client.mjs` and
71
+ * imported directly by `test/account.spec.mjs`, so the tested source is the one
72
+ * the browser runs.
73
+ */
74
+
75
+ /**
76
+ * Build the account request identity for one balance read.
77
+ *
78
+ * The shipped account bundle inlines `DSH_CLIENT_VERSION` at build time; a plugin
79
+ * bundle has no such define, so the build stamps this package's own version and
80
+ * prefers the shell's value when one is inlined.
81
+ *
82
+ * @param version - the version this build stamps.
83
+ * @returns the metadata the account Remote methods carry.
84
+ */
85
+ function clientMetadata(version) {
86
+ const stamped = typeof process !== 'undefined' && process.env !== undefined ? process.env.DSH_CLIENT_VERSION : undefined
87
+ return {
88
+ version: typeof stamped === 'string' && stamped !== '' ? stamped : version,
89
+ locale: typeof navigator !== 'undefined' && typeof navigator.language === 'string' && navigator.language !== ''
90
+ ? navigator.language
91
+ : 'en',
92
+ timezoneOffsetSeconds: -new Date().getTimezoneOffset() * 60,
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Build the one-shot balance reader.
98
+ *
99
+ * @param ctx - the browser plugin context; `ctx.get` resolves an optional service.
100
+ * @param version - the version this build stamps into the account metadata.
101
+ * @returns a reader that resolves to the balance view, or undefined when unavailable.
102
+ */
103
+ function makeBalanceReader(ctx, version, { timeoutMs = 10000 } = {}) {
104
+ const service = name => {
105
+ try {
106
+ return ctx.get(name)
107
+ } catch {
108
+ return undefined
109
+ }
110
+ }
111
+ return async () => {
112
+ const access = service('remoteAccess')
113
+ const remote = service('remote')
114
+ if (access === undefined || remote === undefined) return undefined
115
+ const metadata = clientMetadata(version)
116
+ return await new Promise(resolve => {
117
+ let settled = false
118
+ let stop
119
+ const timer = setTimeout(() => finish(undefined), timeoutMs)
120
+ const finish = value => {
121
+ if (settled) return
122
+ settled = true
123
+ clearTimeout(timer)
124
+ resolve(value)
125
+ if (typeof stop === 'function') {
126
+ try {
127
+ stop()
128
+ } catch {
129
+ /* the grant may already have been released */
130
+ }
131
+ }
132
+ }
133
+ // The read starts before the yield so an effect runner that stops at the
134
+ // first yielded disposer still performs the call.
135
+ stop = access.whenAccess('account/watch', function* () {
136
+ void (async () => {
137
+ try {
138
+ const result = await remote.account.getBalance(metadata)
139
+ const value = result !== undefined && result.ok === true ? result.value : undefined
140
+ finish(value !== undefined && value.status === 'ready' ? value : undefined)
141
+ } catch {
142
+ finish(undefined)
143
+ }
144
+ })()
145
+ yield () => {
146
+ if (!settled) {
147
+ settled = true
148
+ clearTimeout(timer)
149
+ resolve(undefined)
150
+ }
151
+ }
152
+ })
153
+ })
154
+ }
155
+ }
20
156
 
21
157
  /**
22
158
  * AryaCode Polish — the browser face.
@@ -333,6 +469,60 @@ window.__ModuleLoader__.load({
333
469
  : null)
334
470
  }
335
471
 
472
+ /**
473
+ * The remaining-balance line in the sidebar foot.
474
+ *
475
+ * It renders nothing at all when the switch is off, the account exposes no
476
+ * wallet, or the read was refused — an empty line is better than a line that
477
+ * claims a number the Host did not give us.
478
+ *
479
+ * @param props - the slot's `wide` flag, our lazy `configForms`, and the balance reader.
480
+ * @returns the balance line, or null.
481
+ */
482
+ function BalanceLine(props) {
483
+ const enabled = useSwitch(props.configForms, 'accountBalance', true)
484
+ const [wallet, setWallet] = React.useState(undefined)
485
+ const read = props.loadBalance
486
+ React.useEffect(() => {
487
+ if (!enabled || typeof read !== 'function') return undefined
488
+ let live = true
489
+ Promise.resolve()
490
+ .then(() => read())
491
+ .then(view => {
492
+ if (live) setWallet(walletOf(view))
493
+ }, () => {
494
+ if (live) setWallet(undefined)
495
+ })
496
+ return () => {
497
+ live = false
498
+ }
499
+ }, [enabled, read])
500
+ if (!enabled) return null
501
+ // The wrapper stays in the DOM even when there is no number to show, so a
502
+ // reader (and the verification script) can tell "registered but empty" from
503
+ // "not registered at all". An empty flex line costs the layout nothing.
504
+ if (wallet === undefined) {
505
+ return React.createElement('div', { 'data-polish-balance': 'empty', style: { display: 'none' } })
506
+ }
507
+ const money = formatBalance(wallet)
508
+ return React.createElement('div', {
509
+ title: 'Remaining account balance',
510
+ style: {
511
+ display: 'flex',
512
+ alignItems: 'center',
513
+ gap: '6px',
514
+ padding: '2px 8px 4px',
515
+ fontSize: '12px',
516
+ lineHeight: '16px',
517
+ minWidth: 0,
518
+ overflow: 'hidden',
519
+ whiteSpace: 'nowrap',
520
+ textOverflow: 'ellipsis',
521
+ opacity: 0.75,
522
+ },
523
+ }, props.wide === false ? money : 'Balance ' + money)
524
+ }
525
+
336
526
  return {
337
527
  inject: ['slots'],
338
528
  /**
@@ -358,6 +548,13 @@ window.__ModuleLoader__.load({
358
548
  order: 2,
359
549
  inject: () => ({ configForms }),
360
550
  }, CostPill))
551
+ const loadBalance = makeBalanceReader(ctx, CLIENT_VERSION)
552
+ ctx.slots.inject('sidebar.footer.action', () => ctx.slots.register({
553
+ name: 'sidebar.footer.action',
554
+ id: 'polish-balance',
555
+ order: 3,
556
+ inject: () => ({ configForms, loadBalance }),
557
+ }, BalanceLine))
361
558
  },
362
559
  }
363
560
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aryacode/dsh-aryacode-polish",
3
- "version": "0.2.4",
3
+ "version": "0.3.5",
4
4
  "description": "AryaCode Polish: one install for the small things that make AryaCode feel finished. One Plugins row, one switch per quality-of-life feature.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -43,8 +43,16 @@
43
43
  },
44
44
  "client": {
45
45
  "platform": "web"
46
+ },
47
+ "engines": {
48
+ "dsh": "^0.2.1-rc.3"
46
49
  }
47
50
  },
51
+ "scripts": {
52
+ "build:client": "node scripts/build-client.mjs",
53
+ "prepack": "node scripts/build-client.mjs",
54
+ "test": "node --test test/patch.spec.mjs test/config.spec.mjs test/registry.spec.mjs test/cost.spec.mjs test/money.spec.mjs test/account.spec.mjs test/docs.spec.mjs test/manifests.spec.mjs test/client-face.spec.mjs"
55
+ },
48
56
  "dependencies": {
49
57
  "zod": "^4.4.3"
50
58
  },
@@ -63,9 +71,5 @@
63
71
  "./package.json": "./package.json",
64
72
  "./client": "./lib/client.js",
65
73
  "./polish": "./entries/polish.js"
66
- },
67
- "scripts": {
68
- "build:client": "node scripts/build-client.mjs",
69
- "test": "node --test test/patch.spec.mjs test/config.spec.mjs test/registry.spec.mjs test/cost.spec.mjs test/docs.spec.mjs test/manifests.spec.mjs test/client-face.spec.mjs"
70
74
  }
71
- }
75
+ }