@meddleware/token-deployer-ui 0.0.8 → 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.8",
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.27",
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,7 +43,6 @@ 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>
@@ -57,18 +56,32 @@ function onBlur(key: string): void {
57
56
  </UiFieldHint>
58
57
  </template>
59
58
  <template #default="{ attrs }">
60
- <input v-bind="attrs" v-model="form.packageName" placeholder="my_token" autocomplete="off" @blur="onBlur('packageName')" />
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
+ />
61
67
  </template>
62
68
  </UiFormField>
63
69
  <UiFormField id="module" label="Module name" :error="visibleError('moduleName')">
64
70
  <template #label-suffix>
65
71
  <UiFieldHint field-id="module-name">
66
- The Move module inside the package (lowercase, e.g. <b>mytoken</b>). The coin type and
67
- its one-time-witness struct are derived from this name, so it is permanent too.
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.
68
74
  </UiFieldHint>
69
75
  </template>
70
76
  <template #default="{ attrs }">
71
- <input v-bind="attrs" v-model="form.moduleName" placeholder="mytoken" autocomplete="off" @blur="onBlur('moduleName')" />
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
+ />
72
85
  </template>
73
86
  </UiFormField>
74
87
  </div>
@@ -78,8 +91,8 @@ function onBlur(key: string): void {
78
91
  </template>
79
92
  <template v-else>
80
93
  Coin type will be <span class="mono">{{ coinTypePreview }}</span
81
- >. The witness struct <span class="mono">{{ structPreview }}</span> is derived from
82
- 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).
83
96
  </template>
84
97
  </p>
85
98
  </fieldset>
@@ -94,22 +107,35 @@ function onBlur(key: string): void {
94
107
  <UiFormField id="name" label="Token name" :error="visibleError('name')">
95
108
  <template #label-suffix>
96
109
  <UiFieldHint field-id="token-name">
97
- The human-readable display name shown in wallets and explorers (e.g. <b>My Token</b>).
98
- Editable later if you keep the Updatable metadata policy.
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.
99
112
  </UiFieldHint>
100
113
  </template>
101
114
  <template #default="{ attrs }">
102
- <input v-bind="attrs" v-model="form.name" autocomplete="off" @blur="onBlur('name')" />
115
+ <input
116
+ type="text"
117
+ v-bind="attrs"
118
+ v-model="form.name"
119
+ autocomplete="off"
120
+ @blur="onBlur('name')"
121
+ />
103
122
  </template>
104
123
  </UiFormField>
105
124
  <UiFormField id="symbol" label="Symbol" :error="visibleError('symbol')">
106
125
  <template #label-suffix>
107
126
  <UiFieldHint field-id="token-symbol">
108
- The short ticker shown in wallets (e.g. <b>MYT</b>), typically 3–5 uppercase letters.
127
+ The short ticker shown in wallets (e.g. <b>MYT</b>), typically 3–5 uppercase
128
+ letters.
109
129
  </UiFieldHint>
110
130
  </template>
111
131
  <template #default="{ attrs }">
112
- <input v-bind="attrs" v-model="form.symbol" autocomplete="off" @blur="onBlur('symbol')" />
132
+ <input
133
+ type="text"
134
+ v-bind="attrs"
135
+ v-model="form.symbol"
136
+ autocomplete="off"
137
+ @blur="onBlur('symbol')"
138
+ />
113
139
  </template>
114
140
  </UiFormField>
115
141
  </div>
@@ -118,8 +144,8 @@ function onBlur(key: string): void {
118
144
  <template #label-suffix>
119
145
  &nbsp;<span class="hint">(optional)</span>
120
146
  <UiFieldHint field-id="token-description">
121
- A short blurb stored in the coin metadata and shown in some wallets/explorers.
122
- Leave blank if you don't need one.
147
+ A short blurb stored in the coin metadata and shown in some wallets/explorers. Leave
148
+ blank if you don't need one.
123
149
  </UiFieldHint>
124
150
  </template>
125
151
  <template #default="{ attrs }">
@@ -136,7 +162,13 @@ function onBlur(key: string): void {
136
162
  </UiFieldHint>
137
163
  </template>
138
164
  <template #default="{ attrs }">
139
- <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
+ />
140
172
  </template>
141
173
  </UiFormField>
142
174
 
@@ -158,14 +190,21 @@ function onBlur(key: string): void {
158
190
  <UiFormField id="supply" label="Initial supply" :error="visibleError('initialSupply')">
159
191
  <template #label-suffix>
160
192
  <UiFieldHint field-id="supply">
161
- The number of whole tokens to mint and send to the recipient at deployment.
162
- Uses your chosen decimal precision — with 9 decimals, 1 token = 1,000,000,000 base units on-chain.
163
- Enter <b>0</b> or leave blank for no initial supply.
164
- 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.
165
197
  </UiFieldHint>
166
198
  </template>
167
199
  <template #default="{ attrs }">
168
- <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
+ />
169
208
  </template>
170
209
  </UiFormField>
171
210
 
@@ -204,8 +243,8 @@ function onBlur(key: string): void {
204
243
  <UiFormField id="package-policy" label="Package code">
205
244
  <template #label-suffix>
206
245
  <UiFieldHint field-id="package-policy">
207
- <b>Immutable</b>: the UpgradeCap is burned — the on-chain code is sealed.
208
- 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 />
209
248
  <b>Upgradeable</b>: you keep the UpgradeCap and can push code changes to the package
210
249
  after deployment.
211
250
  </UiFieldHint>
@@ -221,14 +260,21 @@ function onBlur(key: string): void {
221
260
  <UiFormField id="recipient" label="Recipient" :error="visibleError('recipient')">
222
261
  <template #label-suffix>
223
262
  <UiFieldHint field-id="recipient">
224
- The Sui address that receives the TreasuryCap, MetadataCap, UpgradeCap (if kept),
225
- and any initial supply minted at deployment.
226
- Leave blank to send everything to the connected wallet.
227
- 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.
228
267
  </UiFieldHint>
229
268
  </template>
230
269
  <template #default="{ attrs }">
231
- <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
+ />
232
278
  </template>
233
279
  </UiFormField>
234
280
 
@@ -236,7 +282,7 @@ function onBlur(key: string): void {
236
282
  </fieldset>
237
283
  </template>
238
284
 
239
- <div class="row" style="margin-top: 1rem">
285
+ <div class="row actions">
240
286
  <button v-if="formStep !== 'identity'" type="button" @click="emit('back')">Back</button>
241
287
  <button type="submit" class="primary" :disabled="!canProceed">
242
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>