Skip to content

feat: 注册流程组件——Checkbox、CodeField、Select / Option、TextField 外观与焦点、Text 内联链接 - #43

Merged
HarlonWang merged 4 commits into
mainfrom
feat/signup-components
Oct 28, 2026
Merged

HarlonWang merged 4 commits into
mainfrom
feat/signup-components

Conversation

@HarlonWang

@HarlonWang HarlonWang commented Oct 28, 2026 •

Copy link
Copy Markdown
Member

一个宿主的注册流程逼出的五项内置组件(M1 第一部分;http tags / crypto.rsaEncrypt / host 保留包名、PageHost 随导航条目存活另起 PR,合齐后发 0.14.0)。

改动

组件 内容
Checkbox checked(必填)、enabled;onClick。与 RadioButton 同模式
TextField 新 prop secure / error / supportingText / prefixText / trailingIcon / imeAction / maxLength;新事件 onFocusChange / onTrailingClick / onPrefixClick。修:首帧未聚焦不再误发 onCommit
CodeField length / secure / enabled / error / autofill(none / oneTimeCode);onChange / onComplete;命令 setText / focus / clear。Compose / M3 无对应组件,框架自绘;短信验证码与交易密码共用,故不叫 Pin / Otp
Select + Option M3 exposed dropdown,选项为 Option children;选中值 prop 叫 value(selected 已是公共布局 prop)
Text 新事件 onLinkClick{id},给了时 [文案](id) 渲染成链接
  • prefixText 画在 leading 槽而非 M3 prefix:后者空且未聚焦时隐藏、不能点
  • autofill:Android 走 semantics SmsOtpCode;iOS 只读 PlatformImeOptions,两种取值都显式设(不设时 NumberPassword 会被推成 Password、弹钥匙串)。真机短信建议待 M3 接真实发码后验
  • CodeField 的 clear / setText 与 TextField.setText 一样触发 onChange,components.md 写了「输错 → 清空并标红」的正确写法

验证

  • pnpm schema:check / runtime:check / typecheck / test,:compose:testAndroidHostTest(新增 LinkedTextTest)、:compose:apiCheck、iOS 编译、:sample:shared:testAndroidHostTest
  • sample-extra 新增 form 页,在 Android 模拟器(Pixel 10)和 iOS 模拟器(iPhone 18 Pro)上逐项点过:邮箱报错态、imeAction next、聚焦提示、显隐切换、区号点击、整行勾选、链接不误触勾选、下拉选择、验证码框 / 交易密码输错标红清空 / 输对完成

🤖 Generated with Claude Code

Summary by Sourcery

Expand the built-in component set and input behaviors to support complete registration forms.

New Features:

  • Add built-in Checkbox, CodeField, Select/Option, and enhanced TextField components for registration flows.
  • Support inline links in Text through an onLinkClick event.
  • Add platform-aware one-time-code autofill behavior for CodeField on Android and iOS.

Bug Fixes:

  • Prevent TextField from emitting an erroneous onCommit event during its initial unfocused composition.
  • Preserve event dispatch order and ensure component command handlers use current state after recomposition.

Enhancements:

  • Expand TextField appearance, focus, input security, length limiting, adornments, and IME behavior.
  • Add CodeField commands, validation states, secure display, completion events, and numeric input handling.
  • Document the new component APIs and registration-flow usage patterns.

Documentation:

  • Update built-in component documentation with the new registration components, properties, events, commands, and usage guidance.

Tests:

  • Add coverage for surrogate-safe text length limiting, inline link parsing and activation, and ordered page event delivery.
  • Add a sample registration form covering field states, checkbox interaction, dropdown selection, links, one-time codes, and transaction PIN handling.

…内联链接

PingPong 注册试点(TinyUI 页做注册)逼出的五项内置组件:

- Checkbox:与 RadioButton 同模式,只报点击,checked 归页面
- TextField:secure(缺省跟 keyboard="password")、error、supportingText、prefixText(leading 槽,可点 onPrefixClick)、trailingIcon + onTrailingClick、imeAction done / next、maxLength;新事件 onFocusChange。顺带修首帧未聚焦被当作失焦、误发 onCommit
- PinField:一排数字格画在一个隐藏输入框上,焦点与 IME 归 Kotlin;onChange / onComplete,命令 setText / focus / clear
- Select + Option:M3 ExposedDropdownMenuBox,选项走 Option children;选中值叫 value(selected 是公共布局 prop)
- Text:给了 onLinkClick 时 `[文案](id)` 渲染成链接

sample-extra 加 form 页演示,Android 模拟器与 iOS 模拟器逐项验过;components.md §3 同步。

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: ebf0d4ab-2f90-40cd-bd45-b39045c31397
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai

sourcery-ai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

本 PR 从 schema 到生成 API、Compose 注册实现、文档和 sample 端到端加入注册流程组件:新增 Checkbox、PinField、Select/Option,扩展 TextField 的外观/焦点/输入行为,并为 Text 提供可选内联链接解析;实现重点是保持交互状态与输入法在 Kotlin 侧、由页面 props/events/commands 驱动状态同步。

Sequence diagram for PIN entry and validation

sequenceDiagram
    participant Page as RegistrationPage
    participant Pin as PinField
    participant Kotlin as KotlinInputState

    Page->>Pin: render secure and error
    Pin->>Kotlin: onValueChange(digits)
    Kotlin-->>Page: onChange(text)
    alt PIN reaches length
        Kotlin-->>Page: onComplete(text)
        Page->>Pin: setText(text) or clear()
        Pin->>Kotlin: update command
        Kotlin-->>Page: onChange(text)
    end
Loading

Sequence diagram for TextField focus and input interactions

sequenceDiagram
    participant Page as RegistrationPage
    participant Field as TextField
    participant Kotlin as KotlinInputState

    Page->>Field: render props
    Field->>Kotlin: onValueChange(text)
    Kotlin-->>Page: onChange(text)
    Kotlin-->>Page: onFocusChange(focused)
    alt IME action is next
        Page->>Field: imeAction=next
        Field-->>Page: onCommit(text)
        Field->>Kotlin: moveFocus(FocusDirection.Next)
    else focus is lost
        Field-->>Page: onCommit(text)
    end
    Page->>Field: setText(text) or focus()
    Field->>Kotlin: update input state
Loading

Flow diagram for controlled Select and inline Text links

flowchart TD
    Select["Select value"] --> Options["Option children"]
    Options --> Dropdown["Exposed dropdown"]
    Dropdown -->|onSelect&#40;value&#41;| Page["Page state"]
    Page -->|updated value| Select
    Text["Text with [label](id)"] -->|onLinkClick&#40;id&#41;| Page
Loading

File-Level Changes

Change Details Files
扩展内置组件 schema、注册表和生成 API,新增注册流程所需的 Checkbox、PinField、Select/Option,并同步 TypeScript/Kotlin 端契约。
  • 定义新增组件的 props、事件、命令及 children/layout 约束。
  • 注册 Compose 实现并更新生成代码、API dump 与运行时字节码。
  • 将 Option 作为 Select 的子节点数据读取,不单独渲染。
schema/components/checkbox.ts
schema/components/pin-field.ts
schema/components/select.ts
schema/components/option.ts
schema/index.ts
compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
compose/src/commonMain/kotlin/app/tinyui/components/generated/BuiltinSchemas.kt
packages/core/src/generated/components.ts
compose/api/compose.klib.api
compose/src/commonMain/kotlin/app/tinyui/generated/RuntimeBytecode.kt
增强 TextField 的输入行为与 Material 外观,同时新增 PinField 的单输入框 PIN 交互实现。
  • 为 TextField 增加安全输入、错误/辅助文案、前后缀、IME next、长度截断及焦点/点击事件。
  • 修正 TextField 首次未聚焦回调误发 onCommit,并保持文本与光标在 Kotlin 侧。
  • 以隐藏 BasicTextField 驱动数字格,支持 secure、enabled、error、setText/focus/clear 及 onChange/onComplete。
compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
schema/components/text-field.ts
schema/components/pin-field.ts
docs/components.md
加入带页面控制选中值的 M3 下拉选择器,并避免与公共 selected 布局 prop 冲突。
  • 使用 ExposedDropdownMenuBox 渲染当前 Option 文案和下拉项。
  • 通过 onSelect 报告 Option value,由页面回写 Select value。
  • 支持 placeholder、label 和 enabled。
compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
schema/components/select.ts
schema/components/option.ts
docs/components.md
为 Text 增加可选的内联链接解析与点击事件。
  • 仅在声明 onLinkClick 时,将紧凑格式 [label](id) 转换为带样式的 LinkAnnotation。
  • 保留不符合语法的方括号文本,并派发链接 id。
  • 增加链接解析、字面量保留和点击回调测试。
compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
schema/components/text.ts
compose/src/commonTest/kotlin/app/tinyui/components/LinkedTextTest.kt
docs/components.md
补充注册流程示例和文档,覆盖新增组件的组合用法及交互约定。
  • 新增 form sample,演示邮箱校验、密码显隐、前缀点击、整行 Checkbox、Select 和 PIN 错误清空流程。
  • 将示例页面加入共享 sample 导航。
  • 更新组件清单、事件/命令语义及 PinField 清错推荐写法。
sample/js-extra/src/pages/form.tsx
sample/shared/src/commonMain/kotlin/app/tinyui/sample/App.kt
docs/components.md
更新现有测试中的运行时输出期望。
  • 同步 PageHostTest 对页面文本版本变化的断言。
compose/src/commonTest/kotlin/app/tinyui/PageHostTest.kt

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@codeseerbot codeseerbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeSeer 审查

结论:批准(附 5 条建议)

摘要

本次改动新增了注册流程所需的组件和 TextField/Text 的交互能力,并同步更新 schema、生成代码及示例。实现整体覆盖了声明的主要功能,但 TextField 的 imeAction="next" 会重复提交,且部分动态 prop 和命令语义没有保持约束。Select 在已展开时切换为禁用状态仍可选择选项,也需要补齐状态收敛。

整个 PR 至 5354c78 · gpt-5.6-terra

Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 11 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="schema/components/pin-field.ts" line_range="7" />
<code_context>
+    doc: "A row of digit cells over one input; text, focus and the IME stay on the Kotlin side like TextField (docs/adr-004 §3.3).",
+    props: {
+        length: number({ default: 6, doc: "number of cells; input stops there" }),
+        secure: boolean({ default: false, doc: "shows a dot instead of each digit" }),
+        enabled: boolean({ default: true }),
+        error: boolean({ default: false, doc: "error colour on the cell outlines" }),
</code_context>
<issue_to_address>
**PIN digits appear unmasked by default**

When a page uses `PinField` without setting `secure` to true, the page author omits `secure`, so the prop defaults to `false` and `PinFieldComponent` renders each entered digit as text; nearby observers can read the PIN on screen.

Default PIN input to masked and require an explicit opt-out to display digits.

Also at `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:299-322`.
</issue_to_address>

### Comment 2
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="307" />
<code_context>
+        onValueChange = { update(it.text) },
+        enabled = scope.get<Boolean>("enabled") ?: true,
+        singleLine = true,
+        keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.NumberPassword, imeAction = ImeAction.Done),
+        keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
+        modifier = scope.modifier(clickable = false).focusRequester(focusRequester).onFocusChanged { focused = it.isFocused },
</code_context>
<issue_to_address>
**Accessibility exposes PIN digits**

When secure is enabled and an accessibility service reads the focused field, the hidden BasicTextField keeps the raw PIN without a password visual transformation, so its text semantics expose the digits to accessibility services.

Apply password masking to the BasicTextField's visual transformation or semantics when secure is enabled.

Also at `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:310`.
</issue_to_address>

### Comment 3
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="340" />
<code_context>
+    val selected = scope.get<String>("value")
+    val enabled = scope.get<Boolean>("enabled") ?: true
+    var expanded by remember { mutableStateOf(false) }
+    ExposedDropdownMenuBox(expanded = expanded, onExpandedChange = { if (enabled) expanded = it }, modifier = scope.modifier(clickable = false)) {
+        OutlinedTextField(
+            value = options.firstOrNull { it.first == selected }?.second ?: "",
</code_context>
<issue_to_address>
**Disabled Select remains actionable**

When enabled changes to false while the Select menu is open, the expanded state remains true and the menu items remain enabled, so users can select an option and dispatch onSelect after the control is disabled.

Close the menu when enabled becomes false and prevent its items from selecting options while disabled.

Also at `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:354`.
</issue_to_address>

### Comment 4
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="261" />
<code_context>
-        visualTransformation = if (keyboard == KeyboardType.Password) PasswordVisualTransformation() else VisualTransformation.None,
-        modifier = scope.modifier(clickable = false).focusRequester(focusRequester).onFocusChanged { if (!it.isFocused) commit() },
+        keyboardOptions = KeyboardOptions(keyboardType = keyboard, imeAction = if (next) ImeAction.Next else ImeAction.Done),
+        keyboardActions = KeyboardActions(onDone = { commit() }, onNext = { commit(); focusManager.moveFocus(FocusDirection.Next) }),
+        visualTransformation = if (secure) PasswordVisualTransformation() else VisualTransformation.None,
+        modifier = scope.modifier(clickable = false).focusRequester(focusRequester).onFocusChanged {
</code_context>
<issue_to_address>
**Next sends duplicate commits**

When the user presses Next and focus moves to another focusable field, keyboardActions calls commit() before moving focus, then the focus-loss handler calls commit() again, so the page receives two onCommit events for one Next action.

Ensure the Next action and focus-loss handler cannot both commit the same field interaction.

Also at `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:268-269`.
</issue_to_address>

### Comment 5
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="218" />
<code_context>
 @Composable
 private fun TextFieldComponent(scope: NodeScope) {
-    var text by remember { mutableStateOf(scope.get<String>("initialText") ?: "") }
+    val maxLength = scope.get<Double>("maxLength")?.toInt()?.takeIf { it > 0 }
+    fun clip(t: String) = if (maxLength != null && t.length > maxLength) t.take(maxLength) else t
+    var text by remember { mutableStateOf(clip(scope.get<String>("initialText") ?: "")) }
</code_context>
<issue_to_address>
**Existing input exceeds new limit**

When a page lowers maxLength or length below the input already held by a mounted field, textField and PinField keep their existing local state without normalizing it to the new limit, so text or PIN digits beyond the limit remain stored and displayed, and PIN digits can reappear if length grows again.

Normalize each field's remembered input whenever its limit prop changes.

Also at `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:219-220`, `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:279`.
</issue_to_address>

### Comment 6
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="289-290" />
<code_context>
+        val changed = t != value.text
+        value = TextFieldValue(t, selection = TextRange(t.length))
+        if (!changed) return
+        if (scope.has("onChange")) scope.dispatch("onChange", payload("text", t))
+        if (t.length == length && scope.has("onComplete")) scope.dispatch("onComplete", payload("text", t))
+    }
+    scope.Commands { command ->
</code_context>
<issue_to_address>
**Callbacks arrive out of order**

When multiple callbacks are dispatched in quick succession and their entry coroutines acquire the runtime in the opposite order, pageHost.dispatch launches each event entry separately, so PIN onComplete can run before onChange and focus-loss onCommit can run before onFocusChange; page state can end up inconsistent.

Serialize event-entry dispatch so callbacks reach the runtime in the order they were dispatched.

Also at `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:267-268`.
</issue_to_address>

### Comment 7
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="279" />
<code_context>
+ */
+@Composable
+private fun PinFieldComponent(scope: NodeScope) {
+    val length = (scope.get<Double>("length")?.toInt() ?: 6).coerceIn(1, 12)
+    var value by remember { mutableStateOf(TextFieldValue("")) }
+    val focusRequester = remember { FocusRequester() }
</code_context>
<issue_to_address>
**Long PINs are truncated**

When a caller configures PinField with length greater than 12, pinFieldComponent clamps length to 12, so the field renders at most twelve cells and update discards input beyond the twelfth digit.

Support the requested PIN length instead of clamping it to 12.
</issue_to_address>

### Comment 8
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="288" />
<code_context>
+        val t = raw.filter { it in '0'..'9' }.take(length)
+        val changed = t != value.text
+        value = TextFieldValue(t, selection = TextRange(t.length))
+        if (!changed) return
+        if (scope.has("onChange")) scope.dispatch("onChange", payload("text", t))
+        if (t.length == length && scope.has("onComplete")) scope.dispatch("onComplete", payload("text", t))
</code_context>
<issue_to_address>
**Unchanged PIN commands emit no event**

When clear is called on an empty PIN or setText supplies the current value, update returns before dispatching onChange when the filtered text equals the current value, so the page receives no event for these commands despite the documented command behavior.

Dispatch onChange for setText and clear commands even when the resulting PIN is unchanged.
</issue_to_address>

### Comment 9
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="227" />
<code_context>
     scope.Commands { command ->
         when (command.name) {
-            "setText" -> { text = command.args["text"] as? String ?: ""; if (scope.has("onChange")) scope.dispatch("onChange", payload("text", text)) }
+            "setText" -> { text = clip(command.args["text"] as? String ?: ""); if (scope.has("onChange")) scope.dispatch("onChange", payload("text", text)) }
             "focus" -> focusRequester.requestFocus()
             "blur" -> focusManager.clearFocus()
</code_context>
<issue_to_address>
**Commands use stale limits**

When `maxLength` or `length` changes while a field remains mounted and a `setText` command arrives afterward, `NodeScope.Commands` runs its handler in a `LaunchedEffect` keyed only by the node, so its first handler keeps the original `maxLength` or `length` closure. Later `setText` commands therefore truncate using the old limit, and callers observe values inconsistent with the current props.

Have `NodeScope.Commands` invoke the latest handler, for example by wrapping it in `rememberUpdatedState`.

Also at `compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:294`.
</issue_to_address>

### Comment 10
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="219" />
<code_context>
 private fun TextFieldComponent(scope: NodeScope) {
-    var text by remember { mutableStateOf(scope.get<String>("initialText") ?: "") }
+    val maxLength = scope.get<Double>("maxLength")?.toInt()?.takeIf { it > 0 }
+    fun clip(t: String) = if (maxLength != null && t.length > maxLength) t.take(maxLength) else t
+    var text by remember { mutableStateOf(clip(scope.get<String>("initialText") ?: "")) }
+    var focused by remember { mutableStateOf(false) }
</code_context>
<issue_to_address>
**Length limit splits emoji**

When `maxLength` truncates input immediately after the first UTF-16 code unit of a supplementary character, such as an emoji, `clip` uses `String.take`, which can cut between a surrogate pair; the field then stores and reports an unpaired surrogate instead of the entered character, so users see corrupted text and callers receive corrupted input.

Truncate at a Unicode code-point boundary, or explicitly define and implement a length unit that cannot split a character.
</issue_to_address>

### Comment 11
<location path="compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt" line_range="319" />
<code_context>
+                        val outline = when { error -> colors.error; current -> colors.primary; else -> colors.outline }
+                        Box(
+                            contentAlignment = Alignment.Center,
+                            modifier = Modifier.weight(1f).height(56.dp).border(if (current || error) 2.dp else 1.dp, outline, RoundedCornerShape(8.dp)),
+                        ) {
+                            if (digit != null) Text(if (secure) "\u2022" else digit.toString(), style = MaterialTheme.typography.headlineSmall)
</code_context>
<issue_to_address>
**PIN cells collapse in unbounded rows**

When pinField is placed inside a horizontally scrolling Row, the cell boxes get their width only from `weight`; Compose ignores Row weights when horizontal constraints are unbounded, so a PinField inside a horizontally scrolling Row renders cells without usable widths.

Give PIN cells a nonzero base width or otherwise size the cell row so it works under unbounded horizontal constraints.
</issue_to_address>

Sourcery assessment

Approval pending. 11 findings to address first.

Blocking findings: schema/components/pin-field.ts:7, compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:307, compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:340, compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:261, compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:218, and 6 more


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread schema/components/code-field.ts
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt Outdated
- Android:semantics contentType = SmsOtpCode
- iOS:Compose 只从 PlatformImeOptions 读 textContentType,不设时按 NumberPassword 推成 Password(交易密码会弹钥匙串);
  所以两种取值都显式设置:oneTimeCode → UITextContentTypeOneTimeCode,none → null
- sample form 页加一个短信验证码示例;components.md 同步

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@codeseerbot codeseerbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeSeer 审查

结论:批准(附 5 条建议)

上轮意见:5 条,已处理 0 条,待处理 5 条

  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:265 [建议] onNext 先调用一次 commit(),随后 moveFocus(FocusDirection.Next) 成功时会触发第 2…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:224 [建议] text 的 remember 没有以 maxLength 为 key,且裁剪只发生在初始化、用户输入和 setText 命令…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:292 [建议] update 在文本未变化时直接返回,所以对已为空的 PinField 执行 clear,或用相同内容执行 setText,都不会…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:284 [建议] length 是可更新的普通 prop,但 value 只在首次组合时创建;当页面把 length 从较大值改小,已有的 `val…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:360 [建议] 下拉菜单已展开后若页面将 enabled 更新为 false,expanded 不会被关闭,而这里的 `DropdownMenuI…

摘要

本次增量为 PinField 增加了 autofill 配置,并在 Android 通过 SmsOtpCode 语义、iOS 通过 PlatformImeOptions 设置一次性验证码内容类型。生成的 Kotlin/TypeScript schema、文档和示例页均已同步该属性。根据给出的代码,未发现本次增量引入的可确认问题。

增量 5354c78..871d0cc · gpt-5.6-terra

@HarlonWang HarlonWang changed the title feat: 注册流程组件——Checkbox、PinField、Select / Option、TextField 外观与焦点、Text 内联链接 feat: 注册流程组件——Checkbox、CodeField、Select / Option、TextField 外观与焦点、Text 内联链接 Oct 28, 2026

@codeseerbot codeseerbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeSeer 审查

结论:批准(附 6 条建议)

上轮意见:5 条,已处理 0 条,待处理 5 条

  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:265 [建议] onNext 先调用一次 commit(),随后 moveFocus(FocusDirection.Next) 成功时会触发第 2…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:224 [建议] text 的 remember 没有以 maxLength 为 key,且裁剪只发生在初始化、用户输入和 setText 命令…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:292 [建议] update 在文本未变化时直接返回,所以对已为空的 PinField 执行 clear,或用相同内容执行 setText,都不会…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:360 [建议] 下拉菜单已展开后若页面将 enabled 更新为 false,expanded 不会被关闭,而这里的 `DropdownMenuI…
  • compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt:284 [建议] length 是可更新的普通 prop,但 value 只在首次组合时创建;当页面把 length 从较大值改小,已有的 `val…

摘要

本次增量将 PinField 统一更名为 CodeField,并同步更新了 schema、Compose 注册、生成的 TS/Kotlin API、运行时字节码、文档和示例。组件实现逻辑基本未变,因此上轮关于动态 length 与内部输入状态不同步的风险仍然存在。

增量 871d0cc..fad69f8 · gpt-5.6-terra

Comment thread compose/src/commonMain/kotlin/app/tinyui/components/Builtins.kt
- PageHost:入口协程走单通道再抢引擎锁,事件不再在多线程池里乱序(新增回归测试,修前 500 个事件必乱)
- Commands:rememberUpdatedState,命令处理器不再捕获首帧的 props(maxLength / length 变了后 setText 用旧上限)
- TextField:imeAction next 只经失焦提交一次 onCommit;maxLength 调小时立即裁剪并报 onChange;截断不切开代理对
- CodeField:setText / clear 无变化也报 onChange(与文档、TextField 一致);length 调小时立即裁剪;secure 时隐藏输入框也做掩码、格子不再重复暴露给读屏;格子最小宽 40dp,滚动 Row 里不塌;length 文档写明 1–12
- Select:展开中被禁用时收起菜单,选项随 enabled 禁用

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@codeseerbot codeseerbot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeSeer 审查

结论:批准

上轮意见:6 条,已处理 6 条,待处理 0 条

摘要

本次增量修复了上轮指出的 TextField IME Next 重复提交、maxLength 收紧后未裁剪,以及 CodeField 命令事件和动态 length 截断问题。Select 在禁用时会关闭已展开菜单并禁用菜单项,避免继续选择。新增的 PageHost 单通道调度使同页 K entry 按入队顺序执行,并补充了相应测试。未发现本次增量引入的其他可确认问题。

增量 fad69f8..99cafc1 · gpt-5.6-terra

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sourcery assessment

Approved.

@HarlonWang
HarlonWang merged commit 4e510b5 into main Oct 28, 2026
5 checks passed
@HarlonWang
HarlonWang deleted the feat/signup-components branch October 28, 2026 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant