Repository navigation
feat: 剪贴板写入、BottomSheet、Divider、按时区格式化日期(0.10.0) - #32
Conversation
PingPong 交易详情页触发 roadmap D 组三项"等第一个需要的页面": - clipboard.setText:J3,框架用 Compose Clipboard 实现,只写不读(读会触发 iOS 粘贴提示), 宿主不接管;页面不在组合里时 E_UNSUPPORTED - BottomSheet 内置组件:M3 ModalBottomSheet,open 由页面掌握,置 false 播完收起动画; 用户下滑 / 点遮罩 / 返回后报 onDismiss;只有展开一档;不接受布局 prop - TinyUIHost.timeZone(IANA,缺省系统时区)+ J2 i18n.timeZone / i18n.zoneOffset + JS i18n.timeZone() / i18n.formatDate(ms, pattern):原生只给那一刻的偏移(含夏令时), 纯数字的 Unicode 模式在 JS 里格式化;时区变化经 K5 i18n.timeZone 重算绑定; 平台不认识的时区按系统时区显示并上报一次 sample 的 features 页加了三项演示;docs 同步 native-api §1/§2/§8/§15、components §3、roadmap D 组。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thickness(默认 1dp)与 color(默认 outlineVariant)同 M3 DividerDefaults;占满宽度,缩进用 paddingHorizontal。竖线(要 IntrinsicSize)与 hairline 不做,见 components.md §3。 sample features 页用上一条。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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. Comment |
Reviewer's Guide本 PR 在 TinyUI Compose 框架中加入只写剪贴板、Material 3 BottomSheet/Divider,以及由原生时区偏移和 JS 数字模式格式化组成的日期能力;同时接入 schema、运行时、文档、测试和 Features sample。 Sequence diagram for timezone-aware date formattingsequenceDiagram
participant Page as TinyUIPage
participant JS as i18n.formatDate
participant Host as PageHost
participant TZ as TinyUIHost.timeZone
participant Platform as Platform timezone API
Page->>JS: formatDate(epochMs, pattern)
JS->>Host: query i18n.zoneOffset(ms)
Host->>TZ: currentTimeZone()
TZ-->>Host: IANA zone
Host->>Platform: zoneOffsetMinutes(zone, epochMs)
Platform-->>Host: offset including DST
Host-->>JS: offset minutes
JS-->>Page: formatted numeric date
TZ-->>Host: zone change
Host-->>JS: i18n.timeZone event
JS-->>Page: bindings recompute
Sequence diagram for framework clipboard writingsequenceDiagram
participant Page as TinyUIPage
participant JS as clipboard.setText
participant Host as PageHost
participant Context as PageContext
participant Clipboard as Compose Clipboard
participant System as System clipboard
Page->>JS: setText(text)
JS->>Host: clipboard.setText
Host->>Context: copy(text)
alt page is on screen
Context->>Clipboard: setClipEntry(plainTextClipEntry(text))
Clipboard->>System: write plain text
System-->>Page: copy resolves
else page is not on screen
Context-->>Page: E_UNSUPPORTED
end
Flow diagram for BottomSheet state and dismissalflowchart TD
Page[Page sets open] --> Open{open}
Open -->|true| Show[Show ModalBottomSheet]
Open -->|false| Hide[Play hide animation]
Hide --> Removed[Leave composition]
Show --> UserDismiss{Swipe scrim or back}
UserDismiss --> Dismiss[onDismiss]
Dismiss --> PageFalse[Page sets open false]
Show --> Close[Page sets open false]
Close --> Hide
Hide --> Reopen{open becomes true during animation}
Reopen -->|yes| Show
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
CodeSeer 审查
结论:批准(附 1 条建议)
摘要
本次 PR 新增了剪贴板写入、BottomSheet、Divider,以及基于宿主时区偏移的日期格式化和对应示例、文档。时区订阅和剪贴板接入路径整体已覆盖到 Kotlin 宿主与 JS runtime。主要风险在于 TinyUIHost 的公开构造函数参数列表发生了不兼容变更,现有宿主升级后可能无法编译或在运行时链接失败。
整个 PR 至 00f7e9c · gpt-5.6-terra
| val capabilities: CapabilityRegistry = CapabilityRegistry(), | ||
| val channels: Map<String, HttpChannel> = emptyMap(), | ||
| val locale: StateFlow<String>? = null, | ||
| val timeZone: StateFlow<String>? = null, |
There was a problem hiding this comment.
[建议] timeZone 被插入到 locale 与 session 之间,删除了旧的 TinyUIHost 构造函数签名。已有宿主若使用位置参数会把 SessionSource 传到 StateFlow<String>? 位置而无法编译;已编译的客户端也找不到旧 ABI 对应的构造函数。应保留一个参数顺序完全一致的旧构造函数(委托给新构造函数并传入 timeZone = null),或至少将新参数追加到末尾并保留旧 ABI 重载。
There was a problem hiding this comment.
保持现状,不加旧签名重载、也不挪到末尾:
- 位置参数:已知宿主(TrendingAI、sample、各测试)
components/sink之后全部是具名参数;locale之后的参数全有默认值且类型各异,按位置传到第五个以上的写法本就不推荐,文档(native-api §1)的示例也是具名 - ABI:挪到末尾同样改变构造函数签名(Kotlin 默认参数的合成构造函数随参数表变),只有保留重载才能保住;而宿主都是从源码编译的 KMP App,没有预编译库链接 tinyui,旧 ABI 没有消费者
- 0.x 的 minor 本就允许这种变化(feat: 框架职责边界落地(ADR-007)——框架自有 http / storage / i18n / 会话 / UI 能力,删 HostServices #28 删
HostServices先例),本 PR 发版时走 minor;timeZone放在locale旁边是为了 i18n 两个参数相邻
🤖 Generated with Claude Code
There was a problem hiding this comment.
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="compose/src/commonMain/kotlin/app/tinyui/TinyUIHost.kt" line_range="28" />
<code_context>
val capabilities: CapabilityRegistry = CapabilityRegistry(),
val channels: Map<String, HttpChannel> = emptyMap(),
val locale: StateFlow<String>? = null,
+ val timeZone: StateFlow<String>? = null,
val session: SessionSource? = null,
val analytics: AnalyticsSink? = null,
</code_context>
<issue_to_address>
**Existing host callers break**
When an existing caller passes constructor arguments positionally or runs previously compiled code against the updated library, `timeZone` shifts positional arguments after `locale`, causing source callers to bind values to the wrong parameters or fail to compile; previously compiled callers can fail with `NoSuchMethodError` because the old constructor signature is gone. Existing host integrations cannot upgrade unchanged.
Add `timeZone` without shifting existing parameters and retain the previous constructor signature with a compatibility overload.
</issue_to_address>
### Comment 2
<location path="packages/native/src/i18n.ts" line_range="60" />
<code_context>
+ zone();
+ if (!Number.isFinite(epochMs)) throw new HostError("E_INVALID", `i18n.formatDate: ${epochMs} is not a time`);
+ // the offset at that instant, so daylight saving follows the date rather than today
+ const d = new Date(epochMs + internal.query<number>("i18n.zoneOffset", { ms: epochMs }) * 60_000);
+ return pattern.replace(TOKEN, (m, quoted: string | undefined) => {
+ if (quoted !== undefined) return quoted === "" ? "'" : quoted.replaceAll("''", "'");
</code_context>
<issue_to_address>
**Old hosts accept incompatible pages**
When a package using these features is delivered to an older host that reports the same unchanged TinyUI version, pages built with these new APIs still advertise the unchanged `tinyui-core` version, so an older host with that version passes the compatibility check despite lacking the new J2 queries, clipboard command, and components. Such pages then format dates incorrectly or fail at runtime instead of being rejected before installation.
Bump the `tinyui-core` version for this runtime API change so packages built with the new APIs fail compatibility checks on older hosts.
Also at `packages/native/src/clipboard.ts:5`.
</issue_to_address>
### Comment 3
<location path="compose/src/commonMain/kotlin/app/tinyui/PageHost.kt" line_range="423-424" />
<code_context>
+ host.locale?.let { flow ->
+ subscriptions.getOrPut("i18n") { follow(flow) { emit("i18n.locale", buildJsonObject { put("locale", it) }.toString()) } }
+ }
+ host.timeZone?.let { flow ->
+ subscriptions.getOrPut("i18n.timeZone") { follow(flow) { emit("i18n.timeZone", buildJsonObject { put("timeZone", it) }.toString()) } }
+ }
}
</code_context>
<issue_to_address>
**System-zone bindings stay stale**
When the host leaves `timeZone` null and the device time zone changes while a page remains mounted, `PageHost` installs no time-zone listener without a host flow, and iOS `systemTimeZone()` reads the cached `NSTimeZone.localTimeZone`. No update refreshes the JS `zone` signal, so `timeZone()` and `formatDate()` bindings keep using the old zone.
Provide a refreshed system-time-zone source and subscribe to it when `host.timeZone` is null.
Also at `packages/native/src/i18n.ts:20-23`, `packages/native/src/i18n.ts:44`, `compose/src/iosMain/kotlin/app/tinyui/Platform.ios.kt:28`.
</issue_to_address>
### Comment 4
<location path="packages/native/src/i18n.ts" line_range="58" />
<code_context>
+export function formatDate(epochMs: number, pattern: string): string {
+ follow();
+ zone();
+ if (!Number.isFinite(epochMs)) throw new HostError("E_INVALID", `i18n.formatDate: ${epochMs} is not a time`);
+ // the offset at that instant, so daylight saving follows the date rather than today
+ const d = new Date(epochMs + internal.query<number>("i18n.zoneOffset", { ms: epochMs }) * 60_000);
</code_context>
<issue_to_address>
**Out-of-range dates produce NaN**
When `formatDate` receives a finite timestamp outside JavaScript’s supported `Date` range, `Number.isFinite` accepts the timestamp, but constructing `d` produces an invalid date whose getters return `NaN`; `formatDate` returns malformed date text instead of rejecting the input.
Reject timestamps outside the supported `Date` range with `E_INVALID` before constructing or formatting the date.
Also at `packages/native/src/i18n.ts:60`.
</issue_to_address>Sourcery assessment
Needs a human reviewer. 4 findings to address first, and the new clipboard capability writes page-controlled text to the system clipboard, where other applications may observe it; reverting the change cannot remove content that was already copied. The BottomSheet, divider, and timezone formatting changes would otherwise be ordinary runtime bugs that revert cleanly.
Blocking findings: compose/src/commonMain/kotlin/app/tinyui/TinyUIHost.kt:28, packages/native/src/i18n.ts:60, compose/src/commonMain/kotlin/app/tinyui/PageHost.kt:424, packages/native/src/i18n.ts:58
Sourcery 审查 #32: - 不给 TinyUIHost.timeZone 时,系统时区变化没有任何推送,iOS 还会缓存 systemTimeZone。 页面重新可见时重读系统时区,变了才推 K5 i18n.timeZone;iOS 读前 resetSystemTimeZone - formatDate 收到超出 ±8.64e15 ms 的时间(或加上时区偏移后越界)会得到全 NaN 的文本,改为 E_INVALID Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
运行时字节码随版本常量重新生成;sample 的宿主快照 4 下限改为 0.10.0(features 页用到 clipboard / formatDate / BottomSheet / Divider 这些新运行时 API)。Sourcery 审查 #32:不升版本时 用了新 API 的包会通过旧宿主的兼容检查。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
CodeSeer 审查
结论:批准(附 2 条建议)
上轮意见:1 条,已处理 0 条,待处理 1 条
compose/src/commonMain/kotlin/app/tinyui/TinyUIHost.kt:28[建议]timeZone被插入到locale与session之间,删除了旧的TinyUIHost构造函数签名。已有宿主若…
摘要
本次增量补上了系统时区变更后的前台重查逻辑,并在 iOS 读取时清理系统时区缓存;同时完善了 formatDate 对 Date 范围的校验。主要风险是新的日期校验改变了非 number 入参的运行时语义,可能将无效值静默格式化。另有版本号实际已从 0.9.2 升至 0.10.0,与 PR 描述中的“版本号未改”不一致。
| { | ||
| "name": "tinyui-core", | ||
| "version": "0.9.2", | ||
| "version": "0.10.0", |
There was a problem hiding this comment.
[细节] PR 描述的验证部分写明“版本号未改”,但这里以及 packages/native、packages/cli 和生成的运行时版本都已改为 0.10.0。请同步 PR 描述和发布说明,或确认此次版本升级是否应当包含在该 PR 中。
There was a problem hiding this comment.
版本升级有意包含在本 PR(与 #28 → 0.8.0 同做法);PR 描述已在审查生成后同步,去掉了版本号未改,并写明 0.10.0 与宿主快照下限。
🤖 Generated with Claude Code
CodeSeer 审查 #32:上一提交用 Date 校验替换了 Number.isFinite,null / true 等会被 Date 强制转换后 当作时间格式化;两项校验都保留。 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
CodeSeer 审查
结论:批准(附 1 条建议)
上轮意见:3 条,已处理 1 条,待处理 2 条
compose/src/commonMain/kotlin/app/tinyui/TinyUIHost.kt:28[建议]timeZone被插入到locale与session之间,删除了旧的TinyUIHost构造函数签名。已有宿主若…packages/core/package.json:3[细节] PR 描述的验证部分写明“版本号未改”,但这里以及packages/native、packages/cli和生成的运行时版本都已改…
摘要
本次增量在 i18n.formatDate 中先使用 Number.isFinite 校验输入,再校验 Date 的可表示范围,避免 JavaScript 对非数值入参进行隐式转换。对应的生成运行时字节码已同步更新,并补充了 null 入参的回归测试。未发现本次增量引入的其他问题。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
CodeSeer 审查
结论:批准(附 1 条建议)
上轮意见:2 条,已处理 0 条,待处理 2 条
compose/src/commonMain/kotlin/app/tinyui/TinyUIHost.kt:28[建议]timeZone被插入到locale与session之间,删除了旧的TinyUIHost构造函数签名。已有宿主若…packages/core/package.json:3[细节] PR 描述的验证部分写明“版本号未改”,但这里以及packages/native、packages/cli和生成的运行时版本都已改…
摘要
本次增量仅将测试中的剪贴板示例标识符从 PP-* 改为 ORD-*,并将文档中的具体业务页面来源改为泛化表述。生产代码和运行时行为未在该增量中改动。未发现由这些修改引入的逻辑、兼容性或文档语义问题。
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
CodeSeer 审查
结论:批准(附 1 条建议)
上轮意见:2 条,已处理 0 条,待处理 2 条
compose/src/commonMain/kotlin/app/tinyui/TinyUIHost.kt:28[建议]timeZone被插入到locale与session之间,删除了旧的TinyUIHost构造函数签名。已有宿主若…packages/core/package.json:3[细节] PR 描述的验证部分写明“版本号未改”,但这里以及packages/native、packages/cli和生成的运行时版本都已改…
摘要
本次增量仅调整了 docs/updates-plan.md 中对受影响用户范围的表述,将“PingPong”替换为“接入的宿主 App”。未见会影响代码行为、构建、兼容性或文档准确性的实际问题。
第一个改造为 TinyUI 的业务页面触发的框架能力:roadmap D 组三项"等第一个需要的页面",外加与 Compose 对齐的
Divider。内容
clipboard.setText(text)(J3),框架用 ComposeClipboard实现,只写不读(读会触发 iOS 粘贴提示),宿主不接管;页面不在组合里 →E_UNSUPPORTED,缺text→E_INVALIDModalBottomSheet;open由页面掌握,置 false 播完收起动画再离开组合;用户下滑 / 点遮罩 / 返回后报onDismiss;收起动画中重新打开会拉回;只有展开一档;不接受布局 propHorizontalDivider,thickness/color默认同DividerDefaults(1dp /outlineVariant);竖线与 hairline 不做TinyUIHost.timeZone: StateFlow<String>?(IANA,缺省系统时区);J2i18n.timeZone/i18n.zoneOffset(那一刻的偏移,含夏令时);JSi18n.timeZone()/i18n.formatDate(ms, pattern),Unicode 模式纯数字子集(yyyy MM M dd d HH H mm ss SSS、'…'字面量),其他字段E_INVALID;时区变化经 K5i18n.timeZone重算绑定;平台不认识的时区按系统时区显示并上报一次i18n.format"不同:原生只给偏移,数字字段在 JS 格式化(无需语言数据、同步、可在 node 测试);文字字段 / 复数 / 数字格式化仍推迟,理由见 native-api.md §8timeZone时系统时区在页面重新可见时重读(iOS 先清缓存);formatDate拒绝超出Date范围的时间TinyUIHost构造参数变化);sample 宿主快照 4 的 tinyui 下限改为 0.10.0验证
:compose:testAndroidHostTest、:compose:apiCheck(apiDump 已更新)、:compose:iosSimulatorArm64Test、:sample:shared:testAndroidHostTest、:sample:androidApp:assembleDebug通过;新增测试覆盖纽约冬夏偏移、时区切换 K5 推送、未知时区回退与只报一次、剪贴板两种错误🤖 Generated with Claude Code
Summary by Sourcery
Deliver the 0.10.0 runtime APIs and built-in UI capabilities needed for clipboard actions, modal bottom sheets, dividers, and timezone-aware date display.
New Features:
Bug Fixes:
Enhancements:
Build:
Documentation:
Tests:
Chores: