Sign in

    petgrowth

    A MoonBit pet growth journal and contextual reminder engine

    pet
    journal
    weight
    pwa
    rabbita
    Download zip
    Version
    0.1.0
    License
    MIT
    Last updated
    5 days ago
    Downloads
    13

    #爪记 · 宠物成长手记

    MoonBit CI

    交付由可复用 MoonBit 核心库、命令行示例和 Rabbita 网页应用组成。根包提供日记、克精度体重、日期排序、JSON 备份校验与问候文案;页面和应用状态也由 MoonBit 实现。JavaScript 仅处理浏览器文件选择、图像压缩和少量浏览器接口。

    一款用 MoonBit 编写核心逻辑和界面的宠物成长 PWA。它帮助养宠人按日期保存日记与照片、记录体重变化,并以宠物口吻生成回访文案。本轮根据 BoopLog 公开的 App 截图重做日记与成长页面:米白纸张、手写字体、灰橄榄色、叠放卡片、照片时光轴与浮动工具栏。代码独立实现,示例宠物图片为 AI 生成,非 BoopLog 官方产品。

    日记页面

    #体验入口

    运行项目后打开 http://127.0.0.1:4173/?demo=1 查看完整示例手帐。打开不带参数的首页使用自己的记录。右上角「更多」可以切换两者。

    示例记录只用于展示,不写入你的数据。新增日记或体重会退出示例模式,保存到自己的手帐;刷新后仍显示自己的记录。

    #已完成

    • 宠物档案:名字、生日、到家日、头像。
    • 成长日记:选择日期,输入文字或添加照片;卡片支持点击、滑动、滚轮和方向键翻阅,点击当前卡片展开详情。
    • 体重记录:按日期录入,在照片时光轴与折线图之间切换;图表支持查看更多记录,点击照片查看详情。
    • 相册:查看日记照片,点击返回记录详情。透明 PNG 上传保留透明背景。
    • 本地数据与迁移:浏览器本地保存,JSON 备份导出与导入。
    • 提醒文案:按距离最近一篇日记的天数生成宠物口吻的问候,并可在档案中开关预览。
    • PWA:支持安装到主屏幕,静态资源可离线打开。

    #当前边界

    照片会在浏览器中压缩后保存到本机存储;清除浏览器数据可能删除记录,请定期导出备份。不同设备之间不会自动同步。iCloud 同步、自动抠图与系统后台推送尚未实现,提醒开关目前只控制应用内的文案预览。请勿把这版演示理解为已具备这些能力。

    #本地运行

    需要 MoonBit CLI 和 Node.js。项目使用 MoonBit 模块依赖 moonbit-community/rabbita@0.16.5。

    moon update moon check --target js --deny-warn moon test --target js --deny-warn node scripts/build.mjs node scripts/serve.mjs

    在浏览器打开 http://127.0.0.1:4173/。app/main.mbt 管理状态与持久化,app/views.mbt 实现页面,app/demo.mbt 提供独立的示例数据。pet.mbt 是可独立测试的日记与提醒核心,web/ 放置静态外壳和浏览器文件接口。scripts/build.mjs 生成 dist/。

    已使用 MoonBit v0.10.15 验证:JavaScript 20 项测试,Wasm GC 与 Wasm 各 18 项测试。后两个后端不包含 2 项浏览器持久化状态测试。统一检查命令:

    node scripts/verify.mjs

    该命令还会核对 命令行示例 的实际输出、构建 PWA 并生成 Mooncakes 源码包。库接口见 API 说明,逐步体验见 评委复现步骤,手机视口检查见 验收记录。iPhone Safari 尚未真机验收。

    #部署

    仓库包含 vercel.json。Vercel 的 Linux 构建步骤通过 MoonBit 官方安装脚本 安装 CLI,再编译到 dist/。这一步依赖构建时能访问 MoonBit 下载服务。部署后需通过 HTTPS 打开才能正常安装 PWA;iPhone Safari 的主屏幕安装和照片选择仍需在真机复核。

    #参赛准备

    公开源码:Rindoncreate/petgrowth。Mooncakes 模块名为 Rindoncreate/petgrowth。源代码采用 MIT 许可证;第三方依赖及字体保留各自许可。

    赛事要求、已完成证据和待办状态见 提交审查清单。正式申报书须由参赛者人工撰写;技术文档与 AI 辅助情况见 AI 使用说明。提交记录随实际开发累积,不以空提交或无意义拆分凑数。

    设计参考与素材许可见 design-reference.md。Yozai 字体按 SIL OFL 1.1 分发,许可证保留在 web/assets/Yozai-OFL.txt。

    DiaryEntry

    pub(all) struct DiaryEntry {
    id : Int
    date : String
    text : String
    photo : String
    } derive(Eq, ToJson,
    FromJson
    )

    DiaryEntry::equal

    fn DiaryEntry::equal(DiaryEntry, DiaryEntry) -> Bool

    DiaryEntry::not_equal

    fn DiaryEntry::not_equal(x : DiaryEntry, y : DiaryEntry) -> Bool

    DiaryEntry::to_json

    fn DiaryEntry::to_json(DiaryEntry) -> Json

    Journal

    pub(all) struct Journal {
    pet : PetProfile
    diaries : Array[DiaryEntry]
    weights : Array[WeightEntry]
    next_id : Int
    reminders_enabled : Bool
    } derive(Eq, ToJson,
    FromJson
    )

    Journal::equal

    fn Journal::equal(Journal, Journal) -> Bool

    Journal::not_equal

    fn Journal::not_equal(x : Journal, y : Journal) -> Bool

    Journal::to_json

    fn Journal::to_json(Journal) -> Json

    PetProfile

    pub(all) struct PetProfile {
    name : String
    birthday : String
    arrival_day : String
    avatar : String
    } derive(Eq, ToJson,
    FromJson
    )

    PetProfile::equal

    fn PetProfile::equal(PetProfile, PetProfile) -> Bool

    PetProfile::not_equal

    fn PetProfile::not_equal(x : PetProfile, y : PetProfile) -> Bool

    PetProfile::to_json

    fn PetProfile::to_json(PetProfile) -> Json

    WeightEntry

    pub(all) struct WeightEntry {
    id : Int
    date : String
    grams : Int
    photo : String
    } derive(Eq, ToJson,
    FromJson
    )

    WeightEntry::equal

    fn WeightEntry::equal(WeightEntry, WeightEntry) -> Bool

    WeightEntry::not_equal

    fn WeightEntry::not_equal(x : WeightEntry, y : WeightEntry) -> Bool

    WeightEntry::to_json

    fn WeightEntry::to_json(WeightEntry) -> Json

    add_diary

    fn add_diary(journal : Journal, date : String, text : String, photo : String) -> Journal?

    add_weight

    fn add_weight(journal : Journal, date : String, grams : Int, photo : String) -> Journal?

    day_number

    fn day_number(iso : String) -> Int?

    decode_journal

    fn decode_journal(raw : String) -> Journal

    diaries_newest_first

    fn diaries_newest_first(journal : Journal) -> Array[DiaryEntry]

    empty_journal

    fn empty_journal() -> Journal

    encode_journal

    fn encode_journal(journal : Journal) -> String

    format_grams_kg

    fn format_grams_kg(grams : Int) -> String

    latest_diary_gap

    fn latest_diary_gap(journal : Journal, today : String) -> Int?

    parse_weight_kg

    fn parse_weight_kg(raw : String) -> Int?

    Parse kilograms as a positive decimal with at most three fractional digits. Values outside 0.001–200 kg, signs and exponent notation are rejected.

    reminder_text

    fn reminder_text(journal : Journal, today : String, variant : Int) -> String?

    remove_diary

    fn remove_diary(journal : Journal, id : Int) -> Journal

    remove_weight

    fn remove_weight(journal : Journal, id : Int) -> Journal

    set_avatar

    fn set_avatar(journal : Journal, avatar : String) -> Journal

    set_pet

    fn set_pet(journal : Journal, name : String, birthday : String, arrival_day : String) -> Journal

    set_reminders

    fn set_reminders(journal : Journal, enabled : Bool) -> Journal

    try_decode_journal

    fn try_decode_journal(raw : String) -> Journal?

    try_set_pet

    fn try_set_pet(journal : Journal, name : String, birthday : String, arrival_day : String) -> Journal?

    Update a profile only when optional dates are valid and ordered.

    weights_oldest_first

    fn weights_oldest_first(journal : Journal) -> Array[WeightEntry]

    Source Files