@meddleware/token-deployer-ui 0.0.7 → 0.0.9

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.
@@ -27,6 +27,15 @@ jobs:
27
27
  - name: Type-check
28
28
  run: npm run type-check
29
29
 
30
+ - name: Lint CSS (stylelint)
31
+ run: npm run lint:css --if-present
32
+
33
+ - name: Lint JS (eslint + vuejs-a11y)
34
+ run: npm run lint:js --if-present
35
+
36
+ - name: Lint HTML (html-validate)
37
+ run: npm run lint:html --if-present
38
+
30
39
  - name: Unit tests
31
40
  run: npm test
32
41
 
@@ -62,8 +62,22 @@ jobs:
62
62
  exit 0
63
63
  fi
64
64
  VERSION=$(node -p "require('./package.json').version")
65
- if npm view "@meddleware/token-deployer-ui@${VERSION}" version >/dev/null 2>&1; then
65
+ NAME=$(node -p "require('./package.json').name")
66
+ ENC=$(printf '%s' "$NAME" | sed 's#/#%2f#')
67
+ # Check the registry directly — `npm view` can transiently miss a freshly
68
+ # first-published version and then attempt a re-publish. A 200 means it's live.
69
+ if [ "$(curl -s -o /dev/null -w '%{http_code}' "https://registry.npmjs.org/${ENC}/${VERSION}")" = "200" ]; then
66
70
  echo "::notice::v${VERSION} already published to npmjs — skipping."
67
71
  else
68
- npm publish
72
+ # Publish, but treat a "version already exists" error as success so a
73
+ # manually-published version (or a registry race) leaves the job green.
74
+ set +e; OUT=$(npm publish 2>&1); RC=$?; set -e
75
+ printf '%s\n' "$OUT"
76
+ if [ "$RC" -ne 0 ]; then
77
+ if printf '%s' "$OUT" | grep -qiE 'cannot publish over|previously published|EPUBLISHCONFLICT'; then
78
+ echo "::notice::v${VERSION} already published — treating as success."
79
+ else
80
+ exit "$RC"
81
+ fi
82
+ fi
69
83
  fi
@@ -0,0 +1,30 @@
1
+ // html-validate over the Vue SFC templates (via html-validate-vue): checks element nesting and
2
+ // content models (e.g. no <p> inside <ul>, no button inside a label) on top of the eslint a11y gate.
3
+ export default {
4
+ plugins: ['html-validate-vue'],
5
+ extends: ['html-validate:recommended', 'html-validate-vue:recommended'],
6
+ elements: [
7
+ 'html5',
8
+ // @meddleware/ui components that render a single native element, described to the validator so
9
+ // content models / form rules see through them (keys are lowercased tag names).
10
+ {
11
+ uibutton: { inherit: 'button' },
12
+ uitoolbarbutton: { inherit: 'button' },
13
+ uiselect: { inherit: 'select' },
14
+ uinotice: { inherit: 'p' },
15
+ },
16
+ ],
17
+ transform: {
18
+ '^.*\\.vue$': 'html-validate-vue',
19
+ },
20
+ rules: {
21
+ // Vue components are written in PascalCase (<UiStatusDot>, <KeepAlive>).
22
+ 'element-case': ['error', { style: ['lowercase', 'pascalcase'] }],
23
+ 'element-name': ['error', { pattern: '^(?:[a-z][a-z0-9\\-._]*-[a-z0-9\\-._]*|[A-Z][A-Za-z0-9]*)$' }],
24
+ // role="list" on styled lists is a deliberate Safari/VoiceOver workaround: WebKit drops list
25
+ // semantics when list-style is removed, so the explicit role must stay (matches the eslint
26
+ // vuejs-accessibility/no-redundant-roles override).
27
+ 'no-redundant-role': 'off',
28
+ 'prefer-native-element': ['error', { exclude: ['list'] }],
29
+ },
30
+ }
package/CLAUDE.md CHANGED
@@ -49,7 +49,7 @@ wallet-adapter `buildExecutor`.
49
49
  - **Orchestration** — [src/lib/deploy.ts](src/lib/deploy.ts) sequences patch →
50
50
  publish → wait → (finalize) via an injectable `Executor` (so it is unit-testable;
51
51
  the real one is built from the wallet in
52
- [src/composables/useWallet.ts](src/composables/useWallet.ts)).
52
+ [src/lib/deployExecutor.ts](src/lib/deployExecutor.ts)).
53
53
  - **Package generation** — [src/lib/generatePackage.ts](src/lib/generatePackage.ts)
54
54
  substitutes the same inputs into the canonical template text
55
55
  ([src/template-src/files.json](src/template-src/files.json)) and zips it.
@@ -0,0 +1,63 @@
1
+ import { globalIgnores } from 'eslint/config'
2
+ import { defineConfigWithVueTs, vueTsConfigs } from '@vue/eslint-config-typescript'
3
+ import pluginVue from 'eslint-plugin-vue'
4
+ import pluginA11y from 'eslint-plugin-vuejs-accessibility'
5
+
6
+ // Flat ESLint config: layers vue-a11y (WAI-ARIA linting for templates) on top of
7
+ // the standard Vue + TypeScript recommended rules, so accessibility regressions
8
+ // are caught in CI.
9
+ export default defineConfigWithVueTs(
10
+ {
11
+ name: 'token-deployer-ui/files-to-lint',
12
+ files: ['**/*.{vue,ts,mts,tsx}'],
13
+ },
14
+
15
+ globalIgnores(['**/dist/**', '**/coverage/**', '**/*.d.ts', 'docs/api/**']),
16
+
17
+ ...pluginVue.configs['flat/essential'],
18
+ ...pluginA11y.configs['flat/recommended'],
19
+ vueTsConfigs.recommended,
20
+
21
+ {
22
+ name: 'token-deployer-ui/overrides',
23
+ rules: {
24
+ // Accept both valid label-association patterns: a label wrapping its
25
+ // control (nesting) or a label[for] pointing at a control[id]. UiSelect renders a native
26
+ // <select>, so a label nesting it is labelling a control.
27
+ 'vuejs-accessibility/label-has-for': [
28
+ 'error',
29
+ { required: { some: ['nesting', 'id'] }, controlComponents: ['UiSelect'] },
30
+ ],
31
+ // UiFormField renders the <label for> for the control in its default slot.
32
+ 'vuejs-accessibility/form-control-has-label': ['error', { labelComponents: ['UiFormField'] }],
33
+ // role="list" on styled lists is a deliberate Safari/VoiceOver workaround: WebKit drops
34
+ // list semantics when list-style is removed, so the explicit role stays.
35
+ 'vuejs-accessibility/no-redundant-roles': 'off',
36
+ // Underscore-prefixed args/vars are an intentional "unused" marker.
37
+ '@typescript-eslint/no-unused-vars': [
38
+ 'error',
39
+ { argsIgnorePattern: '^_', varsIgnorePattern: '^_' },
40
+ ],
41
+ },
42
+ },
43
+
44
+ {
45
+ // Tests use deliberately loose typing for mocks/stubs.
46
+ name: 'token-deployer-ui/test-overrides',
47
+ files: ['**/*.{test,spec}.{ts,tsx}', 'tests/**', 'e2e/**'],
48
+ rules: {
49
+ '@typescript-eslint/no-explicit-any': 'off',
50
+ },
51
+ },
52
+
53
+ {
54
+ // ConfigForm intentionally edits a parent-owned reactive FormModel in place
55
+ // (the parent passes `:form` and reads the same object back). Converting to
56
+ // defineModel is deferred; the in-place mutation is the current contract.
57
+ name: 'token-deployer-ui/config-form',
58
+ files: ['src/components/ConfigForm.vue'],
59
+ rules: {
60
+ 'vue/no-mutating-props': 'off',
61
+ },
62
+ },
63
+ )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meddleware/token-deployer-ui",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -25,22 +25,25 @@
25
25
  "e2e:localnet": "VITE_RPC_TESTNET=${VITE_RPC_TESTNET:-http://127.0.0.1:9000} VITE_FEE_TREASURY_TESTNET=${VITE_FEE_TREASURY_TESTNET:-0xfee0000000000000000000000000000000000000000000000000000000000000} vite build && start-server-and-test preview http://localhost:4173 'E2E_NETWORK=localnet node scripts/e2e-deploy.mjs'",
26
26
  "e2e:testnet": "vite build && start-server-and-test preview http://localhost:4173 'E2E_NETWORK=testnet node scripts/e2e-deploy.mjs'",
27
27
  "e2e:mainnet": "vite build && start-server-and-test preview http://localhost:4173 'E2E_NETWORK=mainnet node scripts/e2e-deploy.mjs'",
28
- "lint": "oxlint . --fix",
28
+ "lint": "npm run lint:css && npm run lint:js && npm run lint:html",
29
29
  "format": "oxfmt src/",
30
30
  "docs": "typedoc",
31
31
  "regen:template": "bash scripts/regen-template.sh",
32
32
  "sync:template": "bash scripts/sync-template-src.sh",
33
33
  "gen:og-image": "node scripts/gen-og-image.mjs",
34
- "verify:template": "node scripts/verify-bytecode.mjs && vitest run tests/templateArtifact.test.ts tests/templateParity.test.ts"
34
+ "verify:template": "node scripts/verify-bytecode.mjs && vitest run tests/templateArtifact.test.ts tests/templateParity.test.ts",
35
+ "lint:css": "stylelint \"src/**/*.{css,vue}\"",
36
+ "lint:js": "eslint .",
37
+ "lint:html": "html-validate \"src/**/*.vue\""
35
38
  },
36
39
  "dependencies": {
37
- "@meddleware/design-tokens": "^0.1.6",
38
- "@meddleware/ui": "^0.1.26",
39
- "@meddleware/wallet-adapter": "^0.0.8",
40
- "@meddleware/walrus-relay": "^0.1.17",
40
+ "@meddleware/design-tokens": "^0.1.7",
41
+ "@meddleware/ui": "^0.1.28",
42
+ "@meddleware/wallet-adapter": "^0.0.9",
43
+ "@meddleware/walrus-relay": "^0.1.18",
41
44
  "@mysten/bcs": "~2.1.0",
42
45
  "@mysten/move-bytecode-template": "~0.4.0",
43
- "@mysten/sui": "^2.31.0",
46
+ "@mysten/sui": "^2.33.0",
44
47
  "@mysten/walrus": "~1.2.0",
45
48
  "fflate": "^0.8.3",
46
49
  "vue": "^3.5.0"
@@ -51,23 +54,41 @@
51
54
  "@vitejs/plugin-vue": "^6.0.8",
52
55
  "@vitejs/plugin-vue-jsx": "^5.1.6",
53
56
  "@vitest/eslint-plugin": "^1.6.23",
57
+ "@vue/eslint-config-typescript": "^14.9.0",
54
58
  "@vue/test-utils": "^2.4.0",
59
+ "autoprefixer": "^10.4.20",
60
+ "eslint": "^10.11.0",
61
+ "eslint-plugin-vue": "^10.11.1",
62
+ "eslint-plugin-vuejs-accessibility": "^2.6.0",
63
+ "html-validate": "^11.16.1",
64
+ "html-validate-vue": "^9.3.1",
65
+ "jiti": "^2.7.0",
55
66
  "jsdom": "^29.1.1",
56
67
  "oxfmt": "^0.59.0",
57
68
  "oxlint": "~1.76.0",
58
69
  "playwright": "^1.62.0",
70
+ "postcss": "^8.4.49",
71
+ "postcss-html": "^2.0.0",
59
72
  "start-server-and-test": "^3.0.11",
73
+ "stylelint": "^17.15.0",
74
+ "stylelint-config-standard": "^40.0.0",
60
75
  "typedoc": "^0.28.20",
61
76
  "typescript": "~6.0.0",
62
77
  "vite": "^8.1.5",
63
78
  "vite-plugin-vue-devtools": "^8.1.5",
64
79
  "vitest": "^4.1.10",
80
+ "vue-eslint-parser": "^10.4.1",
65
81
  "vue-tsc": "~3.3.0"
66
82
  },
67
83
  "optionalDependencies": {
68
- "@meddleware/sui-token-template": "^1.0.1"
84
+ "@meddleware/sui-token-template": "^1.0.4"
69
85
  },
70
86
  "engines": {
71
87
  "node": "^22.18.0 || >=24.12.0"
72
- }
88
+ },
89
+ "browserslist": [
90
+ "> 0.5%",
91
+ "last 2 versions",
92
+ "not dead"
93
+ ]
73
94
  }
@@ -0,0 +1,8 @@
1
+ // Autoprefixer runs over the library's shipped CSS (base.css) during `vite build`,
2
+ // using the `browserslist` field in package.json as the target. Consumers therefore
3
+ // receive vendor-prefixed component CSS without needing their own prefixing step.
4
+ import autoprefixer from 'autoprefixer'
5
+
6
+ export default {
7
+ plugins: [autoprefixer()],
8
+ }
package/src/App.vue CHANGED
@@ -9,12 +9,14 @@ const { mode: colorMode, set: setColorMode } = useColorMode('system')
9
9
  <template>
10
10
  <a class="skip-link" href="#main-content">Skip to main content</a>
11
11
  <main id="main-content">
12
- <div class="theme-bar">
13
- <ColorModeControl :model-value="colorMode" @update:model-value="setColorMode" />
14
- </div>
15
- <header v-once>
12
+ <header class="page-header">
13
+ <ColorModeControl
14
+ class="theme-toggle"
15
+ :model-value="colorMode"
16
+ @update:model-value="setColorMode"
17
+ />
16
18
  <h1>Create your own Sui coin</h1>
17
- <p class="lede">
19
+ <p v-once class="lede">
18
20
  Deploy a coin on Sui directly from your browser. Your wallet signs and pays gas — nothing is
19
21
  compiled or signed on a server — and you keep the full, verifiable source package.
20
22
  </p>
@@ -24,7 +26,7 @@ const { mode: colorMode, set: setColorMode } = useColorMode('system')
24
26
 
25
27
  <TokenDeployerView />
26
28
 
27
- <footer v-once style="margin-top: 3rem; color: var(--muted); font-size: 0.9rem">
29
+ <footer v-once class="page-footer">
28
30
  <p>
29
31
  Client-side token deployment. This tool never takes custody of your token or keys. Symbol,
30
32
  name, and decimals are permanent once published.
@@ -77,12 +77,16 @@ button:disabled {
77
77
  flex-wrap: wrap;
78
78
  align-items: center;
79
79
  }
80
+ /* A row of form/navigation actions below a block of content. */
81
+ .actions {
82
+ margin-top: 1rem;
83
+ }
80
84
  .grid2 {
81
85
  display: grid;
82
86
  grid-template-columns: 1fr;
83
87
  gap: 0 1rem;
84
88
  }
85
- @media (min-width: 520px) {
89
+ @media (width >= 520px) {
86
90
  .grid2 {
87
91
  grid-template-columns: 1fr 1fr;
88
92
  }
@@ -144,18 +148,6 @@ details > fieldset {
144
148
  background: var(--border);
145
149
  }
146
150
 
147
- .visually-hidden {
148
- position: absolute;
149
- width: 1px;
150
- height: 1px;
151
- padding: 0;
152
- margin: -1px;
153
- overflow: hidden;
154
- clip: rect(0 0 0 0);
155
- white-space: nowrap;
156
- border: 0;
157
- }
158
-
159
151
  .skip-link {
160
152
  position: absolute;
161
153
  left: 0.5rem;
@@ -217,31 +209,6 @@ details > fieldset {
217
209
  }
218
210
  }
219
211
 
220
- dialog {
221
- background: var(--surface);
222
- border: 1px solid var(--border);
223
- border-radius: var(--radius);
224
- padding: 1.5rem;
225
- width: min(420px, 90vw);
226
- max-height: 90vh;
227
- overflow-y: auto;
228
- color: var(--text);
229
- }
230
- dialog::backdrop {
231
- background: rgba(0, 0, 0, 0.45);
232
- -webkit-backdrop-filter: blur(2px);
233
- backdrop-filter: blur(2px);
234
- }
235
- .dialog-header {
236
- display: flex;
237
- justify-content: space-between;
238
- align-items: center;
239
- margin-bottom: 1.25rem;
240
- }
241
- .dialog-header h2 {
242
- margin: 0;
243
- font-size: 1.1rem;
244
- }
245
212
  .wallet-list {
246
213
  display: flex;
247
214
  flex-direction: column;
@@ -43,17 +43,46 @@ function onBlur(key: string): void {
43
43
 
44
44
  <template>
45
45
  <form novalidate @submit.prevent="emit('next')">
46
-
47
46
  <!-- Step 1: Identity -->
48
47
  <template v-if="formStep === 'identity'">
49
48
  <fieldset>
50
49
  <legend>Package identity <span class="permanent">(permanent)</span></legend>
51
50
  <div class="grid2">
52
- <UiFormField id="package" label="Package name" :error="visibleError('packageName')" v-slot="{ attrs }">
53
- <input v-bind="attrs" v-model="form.packageName" placeholder="my_token" autocomplete="off" @blur="onBlur('packageName')" />
51
+ <UiFormField id="package" label="Package name" :error="visibleError('packageName')">
52
+ <template #label-suffix>
53
+ <UiFieldHint field-id="package-name">
54
+ The on-chain Move package name (snake_case, e.g. <b>my_token</b>). It identifies the
55
+ published package and cannot be changed after deployment.
56
+ </UiFieldHint>
57
+ </template>
58
+ <template #default="{ attrs }">
59
+ <input
60
+ type="text"
61
+ v-bind="attrs"
62
+ v-model="form.packageName"
63
+ placeholder="my_token"
64
+ autocomplete="off"
65
+ @blur="onBlur('packageName')"
66
+ />
67
+ </template>
54
68
  </UiFormField>
55
- <UiFormField id="module" label="Module name" :error="visibleError('moduleName')" v-slot="{ attrs }">
56
- <input v-bind="attrs" v-model="form.moduleName" placeholder="mytoken" autocomplete="off" @blur="onBlur('moduleName')" />
69
+ <UiFormField id="module" label="Module name" :error="visibleError('moduleName')">
70
+ <template #label-suffix>
71
+ <UiFieldHint field-id="module-name">
72
+ The Move module inside the package (lowercase, e.g. <b>mytoken</b>). The coin type
73
+ and its one-time-witness struct are derived from this name, so it is permanent too.
74
+ </UiFieldHint>
75
+ </template>
76
+ <template #default="{ attrs }">
77
+ <input
78
+ type="text"
79
+ v-bind="attrs"
80
+ v-model="form.moduleName"
81
+ placeholder="mytoken"
82
+ autocomplete="off"
83
+ @blur="onBlur('moduleName')"
84
+ />
85
+ </template>
57
86
  </UiFormField>
58
87
  </div>
59
88
  <p class="hint" aria-live="polite">
@@ -62,8 +91,8 @@ function onBlur(key: string): void {
62
91
  </template>
63
92
  <template v-else>
64
93
  Coin type will be <span class="mono">{{ coinTypePreview }}</span
65
- >. The witness struct <span class="mono">{{ structPreview }}</span> is derived from
66
- the module name (a Sui requirement).
94
+ >. The witness struct <span class="mono">{{ structPreview }}</span> is derived from the
95
+ module name (a Sui requirement).
67
96
  </template>
68
97
  </p>
69
98
  </fieldset>
@@ -75,25 +104,71 @@ function onBlur(key: string): void {
75
104
  <legend>Token details</legend>
76
105
 
77
106
  <div class="grid2">
78
- <UiFormField id="name" label="Token name" :error="visibleError('name')" v-slot="{ attrs }">
79
- <input v-bind="attrs" v-model="form.name" autocomplete="off" @blur="onBlur('name')" />
107
+ <UiFormField id="name" label="Token name" :error="visibleError('name')">
108
+ <template #label-suffix>
109
+ <UiFieldHint field-id="token-name">
110
+ The human-readable display name shown in wallets and explorers (e.g.
111
+ <b>My Token</b>). Editable later if you keep the Updatable metadata policy.
112
+ </UiFieldHint>
113
+ </template>
114
+ <template #default="{ attrs }">
115
+ <input
116
+ type="text"
117
+ v-bind="attrs"
118
+ v-model="form.name"
119
+ autocomplete="off"
120
+ @blur="onBlur('name')"
121
+ />
122
+ </template>
80
123
  </UiFormField>
81
- <UiFormField id="symbol" label="Symbol" :error="visibleError('symbol')" v-slot="{ attrs }">
82
- <input v-bind="attrs" v-model="form.symbol" autocomplete="off" @blur="onBlur('symbol')" />
124
+ <UiFormField id="symbol" label="Symbol" :error="visibleError('symbol')">
125
+ <template #label-suffix>
126
+ <UiFieldHint field-id="token-symbol">
127
+ The short ticker shown in wallets (e.g. <b>MYT</b>), typically 3–5 uppercase
128
+ letters.
129
+ </UiFieldHint>
130
+ </template>
131
+ <template #default="{ attrs }">
132
+ <input
133
+ type="text"
134
+ v-bind="attrs"
135
+ v-model="form.symbol"
136
+ autocomplete="off"
137
+ @blur="onBlur('symbol')"
138
+ />
139
+ </template>
83
140
  </UiFormField>
84
141
  </div>
85
142
 
86
143
  <UiFormField id="description" label="Description" :error="visibleError('description')">
87
- <template #label-suffix>&nbsp;<span class="hint">(optional)</span></template>
144
+ <template #label-suffix>
145
+ &nbsp;<span class="hint">(optional)</span>
146
+ <UiFieldHint field-id="token-description">
147
+ A short blurb stored in the coin metadata and shown in some wallets/explorers. Leave
148
+ blank if you don't need one.
149
+ </UiFieldHint>
150
+ </template>
88
151
  <template #default="{ attrs }">
89
152
  <textarea v-bind="attrs" v-model="form.description" @blur="onBlur('description')" />
90
153
  </template>
91
154
  </UiFormField>
92
155
 
93
156
  <UiFormField id="decimals" label="Decimals" :error="visibleError('decimals')">
94
- <template #label-suffix>&nbsp;<span class="permanent">(permanent)</span></template>
157
+ <template #label-suffix>
158
+ &nbsp;<span class="permanent">(permanent)</span>
159
+ <UiFieldHint field-id="token-decimals">
160
+ How many decimal places the coin supports. <b>9</b> is the Sui convention (1 token =
161
+ 1,000,000,000 base units). Fixed at deployment and cannot be changed.
162
+ </UiFieldHint>
163
+ </template>
95
164
  <template #default="{ attrs }">
96
- <input v-bind="attrs" v-model="form.decimals" inputmode="numeric" @blur="onBlur('decimals')" />
165
+ <input
166
+ type="text"
167
+ v-bind="attrs"
168
+ v-model="form.decimals"
169
+ inputmode="numeric"
170
+ @blur="onBlur('decimals')"
171
+ />
97
172
  </template>
98
173
  </UiFormField>
99
174
 
@@ -115,14 +190,21 @@ function onBlur(key: string): void {
115
190
  <UiFormField id="supply" label="Initial supply" :error="visibleError('initialSupply')">
116
191
  <template #label-suffix>
117
192
  <UiFieldHint field-id="supply">
118
- The number of whole tokens to mint and send to the recipient at deployment.
119
- Uses your chosen decimal precision — with 9 decimals, 1 token = 1,000,000,000 base units on-chain.
120
- Enter <b>0</b> or leave blank for no initial supply.
121
- You can mint more later if you choose the Mintable supply policy.
193
+ The number of whole tokens to mint and send to the recipient at deployment. Uses your
194
+ chosen decimal precision — with 9 decimals, 1 token = 1,000,000,000 base units
195
+ on-chain. Enter <b>0</b> or leave blank for no initial supply. You can mint more later
196
+ if you choose the Mintable supply policy.
122
197
  </UiFieldHint>
123
198
  </template>
124
199
  <template #default="{ attrs }">
125
- <input v-bind="attrs" v-model="form.initialSupply" inputmode="numeric" placeholder="0" @blur="onBlur('initialSupply')" />
200
+ <input
201
+ type="text"
202
+ v-bind="attrs"
203
+ v-model="form.initialSupply"
204
+ inputmode="numeric"
205
+ placeholder="0"
206
+ @blur="onBlur('initialSupply')"
207
+ />
126
208
  </template>
127
209
  </UiFormField>
128
210
 
@@ -161,8 +243,8 @@ function onBlur(key: string): void {
161
243
  <UiFormField id="package-policy" label="Package code">
162
244
  <template #label-suffix>
163
245
  <UiFieldHint field-id="package-policy">
164
- <b>Immutable</b>: the UpgradeCap is burned — the on-chain code is sealed.
165
- Recommended for trust and auditability.<br />
246
+ <b>Immutable</b>: the UpgradeCap is burned — the on-chain code is sealed. Recommended
247
+ for trust and auditability.<br />
166
248
  <b>Upgradeable</b>: you keep the UpgradeCap and can push code changes to the package
167
249
  after deployment.
168
250
  </UiFieldHint>
@@ -178,14 +260,21 @@ function onBlur(key: string): void {
178
260
  <UiFormField id="recipient" label="Recipient" :error="visibleError('recipient')">
179
261
  <template #label-suffix>
180
262
  <UiFieldHint field-id="recipient">
181
- The Sui address that receives the TreasuryCap, MetadataCap, UpgradeCap (if kept),
182
- and any initial supply minted at deployment.
183
- Leave blank to send everything to the connected wallet.
184
- Enter a different address to separate the deployer wallet from the custody wallet.
263
+ The Sui address that receives the TreasuryCap, MetadataCap, UpgradeCap (if kept), and
264
+ any initial supply minted at deployment. Leave blank to send everything to the
265
+ connected wallet. Enter a different address to separate the deployer wallet from the
266
+ custody wallet.
185
267
  </UiFieldHint>
186
268
  </template>
187
269
  <template #default="{ attrs }">
188
- <input v-bind="attrs" v-model="form.recipient" placeholder="0x…" autocomplete="off" @blur="onBlur('recipient')" />
270
+ <input
271
+ type="text"
272
+ v-bind="attrs"
273
+ v-model="form.recipient"
274
+ placeholder="0x…"
275
+ autocomplete="off"
276
+ @blur="onBlur('recipient')"
277
+ />
189
278
  </template>
190
279
  </UiFormField>
191
280
 
@@ -193,7 +282,7 @@ function onBlur(key: string): void {
193
282
  </fieldset>
194
283
  </template>
195
284
 
196
- <div class="row" style="margin-top: 1rem">
285
+ <div class="row actions">
197
286
  <button v-if="formStep !== 'identity'" type="button" @click="emit('back')">Back</button>
198
287
  <button type="submit" class="primary" :disabled="!canProceed">
199
288
  {{ formStep === 'settings' ? 'Review &amp; deploy' : 'Next' }}
@@ -25,9 +25,7 @@ function getState(i: number): 'done' | 'active' | 'todo' {
25
25
  return 'todo'
26
26
  }
27
27
 
28
- const steps = computed(() =>
29
- ORDER.map((s, i) => ({ ...s, state: getState(i) }))
30
- )
28
+ const steps = computed(() => ORDER.map((s, i) => ({ ...s, state: getState(i) })))
31
29
  </script>
32
30
 
33
31
  <template>
@@ -38,7 +36,7 @@ const steps = computed(() =>
38
36
  <span v-if="s.state === 'active'" class="spinner" aria-hidden="true"></span>
39
37
  <span v-else-if="s.state === 'done'" aria-hidden="true">✓ </span>
40
38
  {{ s.label }}
41
- <span v-if="s.state === 'active'" class="visually-hidden">(in progress)</span>
39
+ <span v-if="s.state === 'active'" class="mw-visually-hidden">(in progress)</span>
42
40
  </li>
43
41
  </ol>
44
42
  <p class="hint">Keep this tab open and approve the wallet prompts.</p>
@@ -56,7 +56,7 @@ async function push(): Promise<void> {
56
56
  {{ open ? '▾' : '▸' }} Create a GitHub repo (optional)
57
57
  </button>
58
58
 
59
- <div v-if="open" class="card" style="margin-top: 0.5rem">
59
+ <div v-if="open" class="card github-push__form">
60
60
  <p v-once class="hint">
61
61
  Uses your <strong>own</strong> GitHub token — the repo is created in your account and this
62
62
  site's servers are never involved. The token is sent only to
@@ -67,24 +67,23 @@ async function push(): Promise<void> {
67
67
  </p>
68
68
 
69
69
  <label for="gh-repo">Repository name</label>
70
- <input id="gh-repo" v-model="repoName" autocomplete="off" />
70
+ <input id="gh-repo" v-model="repoName" type="text" autocomplete="off" />
71
71
 
72
72
  <label for="gh-token">GitHub token</label>
73
73
  <input
74
74
  id="gh-token"
75
75
  v-model="token"
76
76
  type="password"
77
- autocomplete="off"
77
+ autocomplete="new-password"
78
78
  spellcheck="false"
79
79
  placeholder="github_pat_… or ghp_…"
80
80
  />
81
81
 
82
- <label style="font-weight: 400; margin-top: 0.75rem">
83
- <input type="checkbox" v-model="isPrivate" style="width: auto" /> Make the repository
84
- private
82
+ <label class="github-push__private">
83
+ <input v-model="isPrivate" type="checkbox" /> Make the repository private
85
84
  </label>
86
85
 
87
- <div class="row" style="margin-top: 0.75rem">
86
+ <div class="row github-push__actions">
88
87
  <button type="button" class="primary" :disabled="busy || !token || !repoName" @click="push">
89
88
  <span v-if="busy" class="spinner" aria-hidden="true"></span> Create repo &amp; push
90
89
  </button>
@@ -98,3 +97,19 @@ async function push(): Promise<void> {
98
97
  </div>
99
98
  </div>
100
99
  </template>
100
+
101
+ <style scoped>
102
+ .github-push__form {
103
+ margin-top: 0.5rem;
104
+ }
105
+ .github-push__private {
106
+ margin-top: 0.75rem;
107
+ font-weight: 400;
108
+ }
109
+ .github-push__private input {
110
+ width: auto;
111
+ }
112
+ .github-push__actions {
113
+ margin-top: 0.75rem;
114
+ }
115
+ </style>