Agent skill

Compose Idle Redraw

by Kototoro-app in Kototoro-app/Kototoro

诊断 Kototoro 主壳"空闲永不定居"式 Compose 自持重绘循环(idle 帧数不为 0). An agent skill from Kototoro-app/Kototoro.

Apache-2.0Auto-check passed

Install Compose Idle Redraw

skills CLI
$ npx skills add Kototoro-app/Kototoro --skill compose-idle-redraw -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install Kototoro-app/Kototoro compose-idle-redraw --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/Kototoro-app/Kototoro.git skills-src && mkdir -p .claude/skills && cp -r skills-src/.claude/skills/compose-idle-redraw .claude/skills/compose-idle-redraw && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
compose-idle-redraw
GitHub stars
613
Token cost
~1.9k tokens
SKILL.md length
427 words
Files
1
Skills in repo
10
Repo updated
First seen
Licence
Apache-2.0

At a glance

诊断 Kototoro 主壳"空闲永不定居"式 Compose 自持重绘循环(idle 帧数不为 0). An agent skill from Kototoro-app/Kototoro.

  • Works in 8 steps: 判定与第一反应 → 测量协议(每次都按这个来,数字才可比) → 归因阶梯(从粗到细,别跳级) → …
  • SKILL.md covers 1. 判定与第一反应, 2. 测量协议(每次都按这个来,数字才可比), 3. 归因阶梯(从粗到细,别跳级) and 4. 三大根因模式(已实证,先对照再创新), plus 4 more sections
  • Calls adb and git

What it does

Compose Idle Redraw is an agent skill from Kototoro-app/Kototoro. 诊断 Kototoro 主壳"空闲永不定居"式 Compose 自持重绘循环(idle 帧数不为 0)。 沉淀自 2026-09 历史页/收藏页 ~620 帧/5s 的整轮调试:gfxinfo 测量协议、快照写入 归因(apply observer)、三大已实证根因模式(SnapshotStateMap put 通知语义 / data-class lambda 字段永不相等 / get-property 每读构造导致 effect 每帧 re-key)、 探针工具箱与修复验证清单。 Triggers: "空闲重绘", "idle frames", "帧数不归零", "一直重绘", "never settles", "gfxinfo frames rendered", "recompose loop", "自持循环", "空闲功耗", "recomposition 不停"

Its SKILL.md is about 1.9k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.

The repository describes itself as: Manga, Novel, and Video reader for Android. The licence is Apache-2.0.

Example prompts

  • “空闲永不定居”
  • “idle frames”
  • “never settles”
  • “/compose-idle-redraw”

Workflow steps

8 steps, taken from the step headings in SKILL.md.

  1. 判定与第一反应
  2. 测量协议(每次都按这个来,数字才可比)
  3. 归因阶梯(从粗到细,别跳级)
  4. 三大根因模式(已实证,先对照再创新)
  5. 探针工具箱(粘贴即用,用完删净)
  6. 修复模式
  7. 修完必做(否则会回归到用户手里)
  8. Kototoro 已排除清单(勿重复劳动)

What it can do on your machine

Read from SKILL.md and the folder at commit 74bd979. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • adb
    • git

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    No URLs in SKILL.md. Its commands use git, which can reach the network depending on how they are called.

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Compose Idle Redraw loads about 1.9k tokens when it runs. Until then it costs about 103 tokens; SKILL.md has 427 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~103
When it runs · the whole SKILL.md, loaded when a task matches
~1.9k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from Kototoro-app/Kototoro at commit 74bd979, republished under its Apache-2.0 licence (© Kototoro-app). 427 words, ~1,868 tokens.

Download SKILL.mdSave it as .claude/skills/compose-idle-redraw/SKILL.md (or your agent's skills folder).
name
compose-idle-redraw
description
诊断 Kototoro 主壳"空闲永不定居"式 Compose 自持重绘循环(idle 帧数不为 0)。 沉淀自 2026-09 历史页/收藏页 ~620 帧/5s 的整轮调试:gfxinfo 测量协议、快照写入 归因(apply observer)、三大已实证根因模式(SnapshotStateMap put 通知语义 / data-class lambda 字段永不相等 / get-property 每读构造导致 effect 每帧 re-key)、 探针工具箱与修复验证清单。 Triggers: "空闲重绘", "idle frames", "帧数不归零", "一直重绘", "never settles", "gfxinfo frames rendered", "recompose loop", "自持循环", "空闲功耗", "recomposition 不停"

Compose 自持重绘循环诊断(idle-redraw feedback loop)

实战锚点:2026-09,history/favourites 页空闲 5s 渲染 620-642 帧而 feed 为 0, 根因为两个独立反馈环,修复 commit 46047d6a7(devel 分支)。归零后全功能回归通过。

1. 判定与第一反应

症状:页面静止、无输入、无动画,dumpsys gfxinfo 的 "Total frames rendered" 持续增长(120Hz 面板上 ≈ 60fps,即 5s 约 600 帧)。伴随现象:耗电、列表屏滚动发热。

第一反应不是找"最热的符号"。实测教训:LazyGridState.applyMeasureResult 占快照 写入 56%,但它只是"每帧重测"的下游后果,不是原因。链条是: 某状态写入 → 祖先重组 → 布局阶段跑 → applyMeasureResult 写快照 → … 追最热符号会带你绕一整圈回到起点。

正确的第一刀是对照组:

  • 有问题的页 vs 恒为 0 的页(feed/订阅页),共用了哪些组件 → 差异即嫌疑面。
  • 根层探针 IdleProbeActivity(已提交,app/src/debug/.../core/dev/IdleProbeActivity.kt, commit 4a124102e):KototoroTheme + 裸 LazyVerticalGrid + 240 静态项。 它 0 帧 → 主题/窗口/insets/系统栏/Application 全部无罪,一次性剪掉大半搜索空间。

2. 测量协议(每次都按这个来,数字才可比)

bash
P=org.skepsun.kototoro.debug
adb shell am force-stop $P
adb shell am start -n $P/org.skepsun.kototoro.main.ui.MainActivity
sleep 8                        # 冷启动稳定
adb shell input tap 342 2577   # 底部导航;Redmi K70 (1280x2772):
                               # history=342, favourites=535, feed=923, 行 y=2577
sleep 6                        # 转场动画结束
adb shell dumpsys gfxinfo $P reset
sleep 5                        # 纯空闲窗口
adb shell dumpsys gfxinfo $P | grep -m1 'Total frames rendered'

规矩:每测一次都 force-stop 重来(进程内状态会互相污染);先 reset 再读增量; 对照组页每轮都测(确认协议本身没坏)。修一处后全部页重测——history 修完后 favourites 还有 624 帧,因为它是第二个独立环,单根因假设会漏。

3. 归因阶梯(从粗到细,别跳级)

  1. 快照 apply observer(最有效的单步):Snapshot.registerApplyObserver 能看到 每个被 apply 的快照实际改变了哪些 state 对象,按对象 identity 计数。反复出现的 对象 = 重组 driver。registerGlobalWriteObserver 看不到嵌套快照(重组协程内) 的写入,别用它做第一步归因。
  2. put 点栈采样:给疑似写入点加 1/40 采样栈打印(见工具箱),拿到完整调用链 Choreographer.doFrame → applyChanges → dispatchSideEffects/RememberObservers → 路由代码 → map.put,直接指认写者。
  3. identityHashCode 探针区分"key 变了"vs"slot churn":对 effect 的每个 key 逐个打 identity。vm 稳定 + callback 稳定 + router 每次新 → 嫌疑立刻收敛到 router。
  4. 布局阶段频率(measure/ogp 每秒次数 vs compose 每秒次数):measure ≈ 2×compose 说明每帧双测(scrollable 内容区 + 外层),是重组的下游证据,不是独立根因。

4. 三大根因模式(已实证,先对照再创新)

模式 A:SnapshotStateMap 的 put 通知语义 + data-class lambda 字段

MutableState.setValue 在值相等时会去重;SnapshotStateMap.put 不会——值相等 也通知所有读者。而路由每次重组都重报状态,data-class 里的 lambda 字段 (onSortOrderSelected、onClick)每次构造都是新实例、用 identity 比较 → == 永远不等 → shell 的相等门形同虚设 → 每次 put 都通知 → shell 重组 → 路由再报。 环闭合条件:写 map 的人(或其祖先)自己也读这个 map。

模式 B:get-property 每读构造 → DisposableEffect 每帧 re-key
kotlin
inline val FragmentActivity.router: AppRouter get() = AppRouter(this)  // 每读一个新实例!

把它直接写进组合,DisposableEffect(appRouter, vm) 的 key 每帧都变: 每帧 forget(onDispose → 报空 → map.remove)+ remember(onRemembered → 报有 → map.put)。这种"在场/缺席交替"是真变化,任何相等门都救不了——必须让 key 稳定 (remember(activity) { activity.router })。无状态 facade 对象都该这么包。

模式 C:反馈环 = 写者的祖先读它写的东西

通用判定:列出"谁写 X、谁读 X"。shell 把路由报告存进 snapshot map,又在自己 scope 里读这个 map 渲染顶栏 → 自持。哪怕写入值不变,map 的通知语义也够点火。

模式 D:无限动画挂在玻璃外壳底下(2026-09-26 实证)

rememberInfiniteTransition 只要有一个值在 draw 阶段被读(哪怕是 graphicsLayer {} 里 几个像素的漂移),整个窗口就按面板刷新率重绘;玻璃顶栏/底栏会采样下层,代价更高。 实例:TopChromeGlow 14s 漂移(0bfb78dfa)让所有一级页空闲 ~130fps、release 进程 空闲 ~42% 单核,两张相隔 1s 的截图却逐像素相同。apply observer 里表现为 唯一一个 Float 状态每帧变,写者栈落在 InfiniteTransition.onFrame。 对策:环境装饰一律静态;确需动效时必须受设置/可见性门控(参照 DiscoverHeroCarousel 的 isPanoramaAnimationEnabled)。排查时 grep rememberInfiniteTransition 最快。

模式 E:组合阶段读 LazyListState.layoutInfo(2026-09-26 实证)

每次测量都会产出新的 LazyListMeasureResult 对象,组合里直接读 layoutInfo 就会 "测量 → 重组 → 子项新 lambda → 重测 → 新 layoutInfo" 自持。apply observer 里表现为 LazyListMeasureResult 状态与某个 …Kt$$ExternalSyntheticLambda 状态每帧同步变化。 实例:订阅页 UpdatedContentCarousel 按偏移算倾斜/宽度(64dad203c)。 对策:用 derivedStateOf 只取需要的数值(偏移表等 data class),空闲时结构相等即不失效。

Show full SKILL.md (159 more words)Show less

5. 探针工具箱(粘贴即用,用完删净)

计数器(树宽 compose/measure 频率)
kotlin
object DevCount {  // 放 app/src/main/.../core/dev/ 或 debug sourceSet
    private val counts = ConcurrentHashMap<String, AtomicLong>()
    private val started = AtomicBoolean(false)
    fun startDumpThread() { if (started.compareAndSet(false, true)) Thread {
        while (true) { Thread.sleep(4000)
            Log.i("KototoroLayoutProbe", "COUNTS " + counts.entries.joinToString(" ") { (k,v) -> "$k=${v.get()}" })
        }
    }.apply { isDaemon = true; name = "devcount" }.start() }
    fun inc(key: String) { startDumpThread(); counts.computeIfAbsent(key){AtomicLong()}.incrementAndGet() }
}
// 用法:composable 体首行 DevCount.inc("favRoute.compose");Modifier.layout 里 inc("grid.measure")
热点栈采样(1/40)
kotlin
fun incWithStack(key: String, tail: Int = 22) {
    inc(key); val n = counts[key]!!.get()
    if (n % 40L == 0L) Log.i(TAG, "STACK $key :: " + Thread.currentThread().stackTrace
        .drop(2).take(tail).joinToString(" < ") { "${it.className.substringAfterLast('.')}.${it.methodName}" })
}

看栈里有没有 dispatchSideEffects(SideEffect 报的)/ DisposableEffectImpl.onRemembered (effect 被 re-remember,key 在变!)/ applyMeasureResult(布局下游,别追)。

apply observer(重组 driver 归因)
kotlin
Snapshot.registerApplyObserver { changed, _ ->
    for (state in changed) {
        val k = state::class.java.name + "@" + Integer.toHexString(System.identityHashCode(state))
        appliedCounts.computeIfAbsent(k){AtomicLong()}.incrementAndGet()
        trackedStates.putIfAbsent(k, state)  // dump 时读 .value 看 VALUE= 是谁
    }
}

输出形如 A#1 x482 ParcelableSnapshotMutableState VALUE=SomeLambda@新地址每帧变。

⚠️ 观察者悖论(大坑)

在首次组合前注册 global write observer 会直接杀死这个环(0 帧 vs 626 帧)—— 它改变了全局快照写入的 apply 时机。所以要用 marker 文件轮询(500ms 查 /data/local/tmp/kototoro_layout_trace 存在才注册),把观察者后挂到已运行的环上。 如果挂上后环死了,本身也是一条诊断信息(环依赖全局快照 apply 时序)。

6. 修复模式

  • 语义等价函数(模式 A 的解):手写 overrideStateEquivalent(a, b) 式比较—— 逐类型比较语义字段(选中数、tab 列表、排序、action 的 title/icon), 刻意忽略回调 identity。安全性论证:存的回调一直引用它捕获的 vm/状态; 语义字段一变,门放行新实例(带新回调),接线不会过期。
  • remember 包 facade(模式 B 的解):无状态、每读构造的对象一律 remember(owner) { ... }。
  • 不要顺手"优化"报告侧(把 SideEffect 换 LaunchedEffect 之类)——报告侧 churn 本身无害,只有写入门 + 祖同读者才成环。动最小面。

7. 修完必做(否则会回归到用户手里)

  1. 全部页 idle 重测归零(含恒 0 对照页,防协议漂移)。
  2. 逐条门控路径功能回归:选择模式计数增减/清除、溢出菜单逐项渲染、 分类 tab 切换后列表内容变化。门最怕"该放行的没放行"。 注意 uiautomator dump 对 Compose 下拉菜单不可靠(内容已渲染但 dump 出旧屏), 用截图确认或直接在数据消费点打点。
  3. logcat 查 FATAL EXCEPTION(门路径空指针类崩溃)。
  4. ./gradlew :app:testDebugUnitTest --no-daemon 过一遍。
  5. 剥净全部探针再提交:git checkout -- 恢复纯探针文件、删未跟踪探针、 手工剔除 fix 文件里的 DevCount 行,commit 只含修复(diff 里 grep DevCount 应为空)。

8. Kototoro 已排除清单(勿重复劳动)

以下已逐一实测或反证排除:系统动画缩放;玻璃/导轨/共享转场、卡片 sharedBounds; AnimatedFaviconDrawable / AnimatedPlaceholderDrawable;RetainedPagingSnapshotController (grep 证明根本未组合,"LazyPagingItems 差异"推论作废);withMacroOptionsFirst (已修缓存但帧数不变);LayerBackdropModifier 几何门控(改了更差,已回退); 主题/窗口/insets/系统栏/Application(IdleProbeActivity 0 帧);外壳/导航/列表屏 (feed 用同一 KototoroContentListScreen 为 0 帧)。 注意:2026-09-26 起 feed 不再是恒 0 对照(见模式 E,已修);对照页改用 IdleProbeActivity。

其他坑:sed 按行号改码极易改出空实验,必须回显补丁内容确认;edit 工具遇 "file changed since read" 就重读再改;先实测再改码——每一条推论都要有 对应的帧数/计数/栈作为证据链。

© Kototoro-app, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

Just SKILL.md in .claude/skills/compose-idle-redraw of Kototoro-app/Kototoro.

Open the folder on GitHubat commit 74bd979

Compare with similar skills

Compose Idle Redraw next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Compose Idle Redraw compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Compose Idle Redraw this skillKototoro-app/Kototoro613—~1.9kAutomated safety check: PassApache-2.0
Docker Composesickn33/agentic-awesome-skills47k1 repos~2.2kAutomated safety check: NotesMIT
Compose Atomslobehub/lobehub83k—~2.6kAutomated safety check: PassCustom licence
Compose Multiplatform Patternsaffaan-m/ECC276k2 repos~1.8kAutomated safety check: PassMIT
Compose Multiplatform Patternsaffaan-m/ECC276k—~1.8kAutomated safety check: PassMIT
Composing Grid CanvasesPostHog/posthog40k—~2.1kAutomated safety check: PassCustom licence

Similar skills

  • Docker Compose

    sickn33/agentic-awesome-skills

    Define and run multi-container Docker applications using Docker Compose.

    47k GitHub starsUsed in 1 repo~2.2k tokens
    DevOps & CloudAuto-check: notes
  • Compose Atoms

    lobehub/lobehub

    Splits a heavy front-end domain into capability atoms that each host imports separately, sinking state into each atom instead of adding mode or readOnly flags.

    83k GitHub stars~2.6k tokensUpdated today
    Frontend & DesignAuto-check passed
  • KMP项目中的Compose Multiplatform和Jetpack Compose模式——状态管理、导航、主题化、性能优化和平台特定UI。

    276k GitHub starsUsed in 2 repos~1.8k tokens
    MobileAuto-check passed
  • KMPプロジェクト向けのCompose MultiplatformおよびJetpack Composeパターン — 状態管理、ナビゲーション、テーマ設定、パフォーマンス、プラットフォーム固有のUI。

    276k GitHub stars~1.8k tokensUpdated today
    MobileAuto-check passed
  • Official

    Compose PostHog grid canvases — widget grids (including the user's home canvas) built from reusable component canvases.

    40k GitHub stars~2.1k tokensUpdated today
    Auto-check passed
  • Compose Catalog

    androidx/androidx

    Build, install, and run the Compose Material Catalog application on connected devices or emulators.

    6.1k GitHub stars~499 tokensUpdated today
    MobileAuto-check passed

More from Kototoro-app/Kototoro

All 10 skills in this repo
  • Android Architecture Reference

    Kototoro-app/Kototoro

    Android 架构、依赖注入、数据层、测试规范官方参考。涵盖 UDF/MVI 模式、Hilt/Dagger、Room、Paging、 DataStore、WorkManager、ViewModel、测试策略等。安卓开发第一要义:参考官方文档和案例!

    613 GitHub stars~1.7k tokensUpdated yesterday
    Auto-check passed
  • Android Compose Reference

    Kototoro-app/Kototoro

    Jetpack Compose 官方示例与最佳实践参考。涵盖 Compose UI 实现、动画、手势、导航、Material3 主题、 性能优化、测试等所有方面。安卓开发第一要义:参考官方文档和案例!

    613 GitHub stars~1.3k tokensUpdated yesterday
    Auto-check passed
  • Android Official Samples

    Kototoro-app/Kototoro

    Android 官方示例代码库完整索引。涵盖架构、Compose UI、性能、测试、存储、网络、相机、媒体、安全等所有领域。

    613 GitHub stars~3.6k tokensUpdated yesterday
    Auto-check passed
  • Kototoro Browse Source Scroll

    Kototoro-app/Kototoro

    Diagnose and fix Kototoro Browse/Explore page content source scrolling jank when many sources exist.

    613 GitHub stars~793 tokensUpdated yesterday
    Auto-check passed
  • Diagnose and fix Kototoro Android issues where returning from reader or video player to details causes flicker, cover reload, panorama reload, progress reset, status bar jump, Compose subtree…

    613 GitHub stars~946 tokensUpdated yesterday
    Auto-check passed
  • Kototoro Main Loading Stability

    Kototoro-app/Kototoro

    Diagnose and fix Kototoro main screen loading flicker, blank flashes, chrome jumps, transient empty states, and route switching flashes.

    613 GitHub stars~904 tokensUpdated yesterday
    Auto-check passed

Questions about Compose Idle Redraw

What does Compose Idle Redraw do?

诊断 Kototoro 主壳"空闲永不定居"式 Compose 自持重绘循环(idle 帧数不为 0). An agent skill from Kototoro-app/Kototoro. Compose Idle Redraw is an agent skill from Kototoro-app/Kototoro.

How do I install Compose Idle Redraw in Claude Code?

Run `npx skills add Kototoro-app/Kototoro --skill compose-idle-redraw -a claude-code`. Or copy the skill folder (.claude/skills/compose-idle-redraw in Kototoro-app/Kototoro) into .claude/skills/compose-idle-redraw in your project. Claude Code loads it when a task matches its description.

How do I install Compose Idle Redraw in Codex?

Run `npx skills add Kototoro-app/Kototoro --skill compose-idle-redraw -a codex`. Or copy the skill folder (.claude/skills/compose-idle-redraw in Kototoro-app/Kototoro) into .agents/skills/compose-idle-redraw in your project. Codex loads it when a task matches its description.

Can I use Compose Idle Redraw in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Kototoro-app/Kototoro --skill compose-idle-redraw -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/compose-idle-redraw, .gemini/skills/compose-idle-redraw, .github/skills/compose-idle-redraw and .opencode/skills/compose-idle-redraw in your project.

What does Compose Idle Redraw need to run?

Going by SKILL.md and its folder, Compose Idle Redraw needs the command-line tools its instructions call (adb and git).

Does Compose Idle Redraw access the network?

SKILL.md contains no URLs. Its commands use git, which can reach the network depending on how they are called. This is read from the text; nothing was executed.

Is Compose Idle Redraw safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Compose Idle Redraw use?

Compose Idle Redraw is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Compose Idle Redraw use?

About 1.9k tokens (SKILL.md is roughly 7.5k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.

What are the alternatives to Compose Idle Redraw?

Skills that share tags, products or a category with Compose Idle Redraw: Docker Compose (sickn33/agentic-awesome-skills, 47k stars), Compose Atoms (lobehub/lobehub, 83k stars), Compose Multiplatform Patterns (affaan-m/ECC, 276k stars) and Compose Multiplatform Patterns (affaan-m/ECC, 276k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Compose Idle Redraw?

Kototoro-app (a GitHub organization) maintains it in Kototoro-app/Kototoro, which has 613 GitHub stars. The repository holds 10 skills in this directory. The repository was last updated on October 9, 2026.

Source: Kototoro-app/Kototoro on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.