[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"navigation":3,"url-settings":86,"blog-\u002Fblog\u002Fproduction-ready-productivity-app":671,"blog-author-\u002Fblog\u002Fproduction-ready-productivity-app":1333,"i-material-symbols:arrow-back-rounded":1348},{"id":4,"extension":5,"footer":6,"header":73,"meta":83,"stem":84,"__hash__":85},"navigation\u002Fdata\u002Fshared\u002Fnavigation.yml","yml",{"brand":7,"columns":13,"legal":63},{"name":8,"tagline":9,"downloadCta":10},"Pieces","The memory layer for modern work.",{"label":11,"href":12},"All downloads","\u002Fdownloads",[14,26,39,51],{"title":15,"links":16},"Product",[17,20,23],{"label":18,"href":19},"Pieces Desktop","\u002F",{"label":21,"href":22},"Pieces Enterprise","\u002Fenterprise",{"label":24,"href":25},"Pieces MCP","\u002Fmcp",{"title":27,"links":28},"Resources",[29,33,36],{"label":30,"href":31,"external":32},"Documentation","url:docs.home",true,{"label":34,"href":35},"Blog","\u002Fblog",{"label":37,"href":38,"external":32},"GitHub","url:github.org",{"title":40,"links":41},"Community",[42,45,48],{"label":43,"href":44,"external":32},"Discord","url:social.discord",{"label":46,"href":47,"external":32},"X \u002F Twitter","url:social.x",{"label":49,"href":50,"external":32},"LinkedIn","url:social.linkedin",{"title":52,"links":53},"Company",[54,57,60],{"label":55,"href":56},"About","\u002Fabout",{"label":58,"href":59},"Updates","\u002Fupdates",{"label":61,"href":62},"Contact","\u002Fcontact",[64,67,70],{"label":65,"href":66},"Privacy Policy","\u002Flegal\u002Fprivacy",{"label":68,"href":69},"Refund Policy","\u002Flegal\u002Frefund",{"label":71,"href":72},"Terms of Service","\u002Flegal\u002Fterms",{"links":74,"signIn":75,"contact":78,"cta":80},[],{"label":76,"href":77},"Manage account","url:portal.home",{"label":79,"href":62},"Get in touch",{"label":81,"href":82},"Download","url:routes.downloads",{},"data\u002Fshared\u002Fnavigation","XlSALC7vYCXHcmcXnjJViwIZKsqnQWF4bn4o9RrcSBE",{"id":87,"extension":5,"links":88,"meta":668,"stem":669,"__hash__":670},"urlSettings\u002Fdata\u002Fshared\u002Furls.yml",[89,93,97,101,105,109,113,117,121,125,129,133,137,141,145,149,153,157,161,165,169,173,177,181,185,189,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,256,260,264,268,272,276,280,284,288,292,296,300,303,307,311,314,318,322,326,330,334,338,342,346,350,354,358,362,366,370,374,378,382,386,390,394,398,402,406,410,414,418,422,426,430,434,438,442,446,450,454,458,461,465,469,473,477,481,485,488,491,494,497,501,505,508,512,516,520,524,528,532,536,540,544,548,552,556,559,563,567,571,575,579,582,585,588,592,595,599,603,607,611,614,618,622,625,628,631,635,639,643,646,649,652,656,660,664],{"key":90,"label":91,"href":92},"downloads.desktop","Desktop download page","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdownload",{"key":94,"label":95,"href":96},"downloads.macOS.dmgArm64","macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg-arm64\u002Fdownload",{"key":98,"label":99,"href":100},"downloads.macOS.dmgIntel","macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg\u002Fdownload",{"key":102,"label":103,"href":104},"downloads.macOS.pkg","macOS PKG","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg\u002Fdownload",{"key":106,"label":107,"href":108},"downloads.macOS.universal","Desktop macOS Universal","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fmacos-universal\u002Fdownload",{"key":110,"label":111,"href":112},"downloads.macOS.pkgArm64","Desktop macOS PKG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pfd-arm64\u002Fdownload",{"key":114,"label":115,"href":116},"downloads.macOS.pkgIntel","Desktop macOS PKG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pfd\u002Fdownload",{"key":118,"label":119,"href":120},"downloads.windows.appinstaller","Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fpieces_for_x.appinstaller",{"key":122,"label":123,"href":124},"downloads.windows.exe","Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fwindows-exe\u002Fdownload",{"key":126,"label":127,"href":128},"downloads.windows.msix","Windows MSIX","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fwindows-msix\u002Fdownload",{"key":130,"label":131,"href":132},"downloads.windows.suiteManager","Windows Suite Manager","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_suite_windows\u002Fappinstaller\u002Fdownload",{"key":134,"label":135,"href":136},"downloads.linux.flatpakRepo","Linux Flatpak repository","https:\u002F\u002Fbuilds.pieces.app\u002Fpieces-flatpak-repo\u002Fpieces-flatpak.flatpakrepo",{"key":138,"label":139,"href":140},"downloads.linux.snapDesktop","Linux Snap Desktop","https:\u002F\u002Fsnapcraft.io\u002Fpieces-for-developers",{"key":142,"label":143,"href":144},"downloads.linux.snapPiecesOS","Linux Snap PiecesOS","https:\u002F\u002Fsnapcraft.io\u002Fpieces-os",{"key":146,"label":147,"href":148},"downloads.linux.snap","Desktop Linux Snap package","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fsnap\u002Fdownload",{"key":150,"label":151,"href":152},"downloads.piecesOS.macOS.dmgArm64","PiecesOS macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg-arm64\u002Fdownload",{"key":154,"label":155,"href":156},"downloads.piecesOS.macOS.dmgIntel","PiecesOS macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg\u002Fdownload",{"key":158,"label":159,"href":160},"downloads.piecesOS.macOS.universal","PiecesOS macOS Universal","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fmacos-universal\u002Fdownload",{"key":162,"label":163,"href":164},"downloads.piecesOS.macOS.pkgArm64","PiecesOS macOS PKG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pos-launch-only-arm64\u002Fdownload",{"key":166,"label":167,"href":168},"downloads.piecesOS.macOS.pkgIntel","PiecesOS macOS PKG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-pos-launch-only\u002Fdownload",{"key":170,"label":171,"href":172},"downloads.piecesOS.windows.appinstaller","PiecesOS Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fos_server.appinstaller",{"key":174,"label":175,"href":176},"downloads.piecesOS.windows.exe","PiecesOS Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fwindows-exe\u002Fdownload",{"key":178,"label":179,"href":180},"downloads.piecesOS.windows.msix","PiecesOS Windows MSIX","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fwindows-msix\u002Fdownload",{"key":182,"label":183,"href":184},"downloads.piecesOS.linux.snap","PiecesOS Linux Snap package","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fsnap\u002Fdownload",{"key":186,"label":187,"href":188},"downloads.combined.macOS.appleSilicon","Combined macOS installer Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg-arm64\u002Fdownload",{"key":190,"label":191,"href":104},"downloads.combined.macOS.intel","Combined macOS installer Intel",{"key":193,"label":194,"href":195},"downloads.guides.macOS","macOS installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fmacos-installation-guide",{"key":197,"label":198,"href":199},"downloads.guides.windows","Windows installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fwindows-installation-guide",{"key":201,"label":202,"href":203},"downloads.guides.linux","Linux installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide",{"key":205,"label":206,"href":207},"downloads.guides.linuxFlatpak","Linux Flatpak installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide\u002Fflatpak",{"key":209,"label":210,"href":211},"downloads.guides.linuxSnap","Linux Snap installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide\u002Fsnap",{"key":213,"label":214,"href":215},"downloads.guides.piecesOS","PiecesOS manual installation","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fmanual-installation",{"key":217,"label":218,"href":219},"extensions.chrome","Chrome extension","https:\u002F\u002Fchrome.google.com\u002Fwebstore\u002Fdetail\u002Fpieces-save-code-snippets\u002Figbgibhbfonhmjlechmeefimncpekepm",{"key":221,"label":222,"href":223},"extensions.firefox","Firefox add-on","https:\u002F\u002Faddons.mozilla.org\u002Fen-US\u002Ffirefox\u002Faddon\u002Fpieces-save-code-from-the-web\u002F",{"key":225,"label":226,"href":227},"extensions.edge","Edge add-on","https:\u002F\u002Fmicrosoftedge.microsoft.com\u002Faddons\u002Fdetail\u002Fpieces-save-code-snippet\u002Fhglfimcdgonaeeobjckfdabcldfidmim",{"key":229,"label":230,"href":231},"extensions.vscode","VS Code extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.pieces-vscode",{"key":233,"label":234,"href":235},"extensions.visualStudio","Visual Studio extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.PiecesVisualStudio",{"key":237,"label":238,"href":239},"extensions.jetbrains","JetBrains plugin","https:\u002F\u002Fplugins.jetbrains.com\u002Fplugin\u002F17328-pieces--save-search-share--reuse-code-snippets",{"key":241,"label":242,"href":243},"extensions.obsidian","Obsidian plugin","https:\u002F\u002Fobsidian.md\u002Fplugins?id=pieces-for-developers",{"key":245,"label":246,"href":247},"extensions.sublime","Sublime package","https:\u002F\u002Fpackagecontrol.io\u002Fpackages\u002FPieces",{"key":249,"label":250,"href":251},"extensions.neovim","Neovim plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neo_vim",{"key":253,"label":254,"href":255},"extensions.jupyterlab","JupyterLab plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fjupyterlab-pieces",{"key":257,"label":258,"href":259},"extensions.cli","Pieces CLI","https:\u002F\u002Fpypi.org\u002Fproject\u002Fpieces-cli\u002F",{"key":261,"label":262,"href":263},"docs.home","Documentation home","https:\u002F\u002Fdocs.pieces.app",{"key":265,"label":266,"href":267},"docs.getStarted","Get started docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces",{"key":269,"label":270,"href":271},"docs.api","API docs","https:\u002F\u002Fdocs.pieces.app\u002Fapi",{"key":273,"label":274,"href":275},"docs.desktop.overview","Desktop overview","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop",{"key":277,"label":278,"href":279},"docs.desktop.onboarding","Desktop onboarding","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fonboarding",{"key":281,"label":282,"href":283},"docs.desktop.timeline","Desktop timeline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Ftimeline",{"key":285,"label":286,"href":287},"docs.desktop.summaries","Desktop summaries docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fsingle-click-summaries",{"key":289,"label":290,"href":291},"docs.desktop.search","Desktop conversational search docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconversational-search",{"key":293,"label":294,"href":295},"docs.desktop.drive","Desktop drive docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdrive",{"key":297,"label":298,"href":299},"docs.desktop.account","Desktop account settings docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconfiguration\u002Faccount",{"key":301,"label":302,"href":92},"docs.desktop.download","Desktop download docs",{"key":304,"label":305,"href":306},"docs.piecesOS.overview","PiecesOS overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies",{"key":308,"label":309,"href":310},"docs.piecesOS.details","PiecesOS details docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os",{"key":312,"label":313,"href":215},"docs.piecesOS.install","PiecesOS install docs",{"key":315,"label":316,"href":317},"docs.piecesOS.quickMenu","PiecesOS quick menu docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fquick-menu",{"key":319,"label":320,"href":321},"docs.piecesOS.storage","On-device storage docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fon-device-storage",{"key":323,"label":324,"href":325},"docs.piecesOS.troubleshooting","PiecesOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Ftroubleshooting",{"key":327,"label":328,"href":329},"docs.mcp.overview","MCP overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp",{"key":331,"label":332,"href":333},"docs.mcp.cursor","MCP Cursor docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcursor",{"key":335,"label":336,"href":337},"docs.mcp.vscode","MCP VS Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fvs-code",{"key":339,"label":340,"href":341},"docs.mcp.claudeDesktop","MCP Claude Desktop docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-desktop",{"key":343,"label":344,"href":345},"docs.mcp.claudeCode","MCP Claude Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-code",{"key":347,"label":348,"href":349},"docs.mcp.claudeCowork","MCP Claude Cowork docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-cowork",{"key":351,"label":352,"href":353},"docs.mcp.githubCopilot","MCP GitHub Copilot docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgithub-copilot",{"key":355,"label":356,"href":357},"docs.mcp.goose","MCP Goose docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoose",{"key":359,"label":360,"href":361},"docs.mcp.windsurf","MCP Windsurf docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fwindsurf",{"key":363,"label":364,"href":365},"docs.mcp.zed","MCP Zed docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fzed",{"key":367,"label":368,"href":369},"docs.mcp.jetbrains","MCP JetBrains docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fjetbrains-ides",{"key":371,"label":372,"href":373},"docs.mcp.continueDev","MCP Continue docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcontinue-dev",{"key":375,"label":376,"href":377},"docs.mcp.cline","MCP Cline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcline",{"key":379,"label":380,"href":381},"docs.mcp.raycast","MCP Raycast docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fraycast",{"key":383,"label":384,"href":385},"docs.mcp.rovoDevCli","MCP Rovo Dev CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Frovo-dev-cli",{"key":387,"label":388,"href":389},"docs.mcp.openaiCodexCli","MCP OpenAI Codex CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenai-codex-cli",{"key":391,"label":392,"href":393},"docs.mcp.googleGeminiCli","MCP Google Gemini CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoogle-gemini-cli",{"key":395,"label":396,"href":397},"docs.mcp.amazonQ","MCP Amazon Q docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Famazon-q-developer",{"key":399,"label":400,"href":401},"docs.mcp.chatgptDev","MCP ChatGPT Developer Mode docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fchatgpt-developer-mode",{"key":403,"label":404,"href":405},"docs.mcp.openclaw","MCP OpenClaw docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenclaw",{"key":407,"label":408,"href":409},"docs.mcp.mcpRemote","MCP Remote docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fmcp-remote",{"key":411,"label":412,"href":413},"docs.mcp.ngrok","MCP ngrok docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fngrok-setup",{"key":415,"label":416,"href":417},"docs.troubleshooting.macOS","macOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fmacos",{"key":419,"label":420,"href":421},"docs.troubleshooting.windows","Windows troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fwindows",{"key":423,"label":424,"href":425},"docs.troubleshooting.linux","Linux troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Flinux",{"key":427,"label":428,"href":429},"docs.privacy","Privacy and security docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fprivacy-security-your-data",{"key":431,"label":432,"href":433},"docs.support","Support docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fsupport",{"key":435,"label":436,"href":437},"portal.home","Pieces portal","https:\u002F\u002Fportal.pieces.app",{"key":439,"label":440,"href":441},"site.home","Website home","https:\u002F\u002Fpieces.app",{"key":443,"label":444,"href":445},"site.about","About page","https:\u002F\u002Fpieces.app\u002Fabout",{"key":447,"label":448,"href":449},"site.features","Features page","https:\u002F\u002Fpieces.app\u002Ffeatures",{"key":451,"label":452,"href":453},"site.plugins","Plugins page","https:\u002F\u002Fpieces.app\u002Fplugins",{"key":455,"label":456,"href":457},"site.contact","Contact page","https:\u002F\u002Fpieces.app\u002Fcontact",{"key":459,"label":58,"href":460},"site.updates","https:\u002F\u002Fpieces.app\u002Fupdates",{"key":462,"label":463,"href":464},"site.news","News","https:\u002F\u002Fpieces.app\u002Fnews",{"key":466,"label":467,"href":468},"site.events","Community events","https:\u002F\u002Fpieces.app\u002Fcommunity\u002Fevents",{"key":470,"label":471,"href":472},"site.userStories","User stories","https:\u002F\u002Fpieces.app\u002Fuser-stories",{"key":474,"label":475,"href":476},"site.academy","Academy","https:\u002F\u002Fpieces.app\u002Flearn\u002Facademy",{"key":478,"label":479,"href":480},"site.support","Website support","https:\u002F\u002Fpieces.app\u002Fsupport",{"key":482,"label":483,"href":484},"site.standup","Standup","https:\u002F\u002Fpieces.app\u002Fstandup",{"key":486,"label":34,"href":487},"site.blog","https:\u002F\u002Fcode.pieces.app\u002Fblog",{"key":489,"label":43,"href":490},"social.discord","https:\u002F\u002Fdiscord.gg\u002Fgetpieces",{"key":492,"label":46,"href":493},"social.x","https:\u002F\u002Fx.com\u002Fgetpieces",{"key":495,"label":496,"href":493},"social.twitter","Twitter",{"key":498,"label":499,"href":500},"social.instagram","Instagram","https:\u002F\u002Fwww.instagram.com\u002Fgetpieces\u002F",{"key":502,"label":503,"href":504},"social.tiktok","TikTok","https:\u002F\u002Fwww.tiktok.com\u002F@getpieces",{"key":506,"label":49,"href":507},"social.linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fgetpieces\u002F",{"key":509,"label":510,"href":511},"social.youtube","YouTube","https:\u002F\u002Fyoutube.com\u002F@getpieces",{"key":513,"label":514,"href":515},"github.org","GitHub organization","https:\u002F\u002Fgithub.com\u002Fpieces-app",{"key":517,"label":518,"href":519},"github.support","GitHub support","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport",{"key":521,"label":522,"href":523},"github.issues","GitHub issues","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fissues",{"key":525,"label":526,"href":527},"github.discussions","GitHub discussions","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fdiscussions",{"key":529,"label":530,"href":531},"github.documentation","GitHub documentation","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fdocumentation",{"key":533,"label":534,"href":535},"github.opensource","GitHub open source","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fopensource",{"key":537,"label":538,"href":539},"github.sdks.python","Python SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-python",{"key":541,"label":542,"href":543},"github.sdks.typescript","TypeScript SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-typescript",{"key":545,"label":546,"href":547},"github.sdks.dart","Dart SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-dart",{"key":549,"label":550,"href":551},"github.sdks.kotlin","Kotlin SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-kotlin",{"key":553,"label":554,"href":555},"github.plugins.obsidian","Obsidian plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fobsidian-pieces",{"key":557,"label":558,"href":255},"github.plugins.jupyterlab","JupyterLab plugin repository",{"key":560,"label":561,"href":562},"github.plugins.sublime","Sublime plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_sublime",{"key":564,"label":565,"href":566},"github.plugins.neovim","Neovim plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neovim",{"key":568,"label":569,"href":570},"github.cliAgent","CLI agent repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fcli-agent",{"key":572,"label":573,"href":574},"github.mcpDart","MCP Dart repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fmcp_dart",{"key":576,"label":577,"href":578},"github.awesomePieces","Awesome Pieces repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fawesome-pieces",{"key":580,"label":581,"href":66},"legal.privacyPolicy","Privacy policy",{"key":583,"label":584,"href":69},"legal.refundPolicy","Refund policy",{"key":586,"label":587,"href":72},"legal.terms","Terms",{"key":589,"label":590,"href":591},"legal.security","Legal security","https:\u002F\u002Fpieces.app\u002Flegal\u002Fsecurity",{"key":593,"label":594,"href":511},"videos.youtubeChannel","YouTube channel",{"key":596,"label":597,"href":598},"videos.gettingStartedDesktop","Getting started desktop video","https:\u002F\u002Fyoutu.be\u002FdUr1lRM_TYk",{"key":600,"label":601,"href":602},"videos.snippetDiscoveryxR","Snippet discovery video","https:\u002F\u002Fyoutu.be\u002FG6vb1USw-30",{"key":604,"label":605,"href":606},"sales.bookACall","Book a sales call","https:\u002F\u002Fcalendar.app.google\u002FWVUDtUfNy5Vst3sH7",{"key":608,"label":609,"href":610},"sales.enterprise","Enterprise form","https:\u002F\u002Fgetpieces.typeform.com\u002Fto\u002FaVQFTvpE",{"key":612,"label":613,"href":527},"sales.feedback","Feedback discussions",{"key":615,"label":616,"href":617},"sales.earlyAccess","Early access form","https:\u002F\u002Fgetpieces.typeform.com\u002Fearlyaccess",{"key":619,"label":620,"href":621},"sales.supportEmail","Support email","mailto:support@pieces.app",{"key":623,"label":624,"href":19},"routes.home","Home route",{"key":626,"label":627,"href":56},"routes.about","About route",{"key":629,"label":630,"href":12},"routes.downloads","Downloads route",{"key":632,"label":633,"href":634},"routes.thanks","Post-install thanks route","\u002Finstall\u002Fthanks",{"key":636,"label":637,"href":638},"routes.pricing","Pricing route","\u002Fpricing",{"key":640,"label":641,"href":642},"cta.freeTrial","Start free trial CTA (Pieces Pro) — used by \u002Fcampaigns\u002F* landing pages","https:\u002F\u002Fcampaigns.pieces.app\u002F",{"key":644,"label":645,"href":22},"routes.enterprise","Enterprise route",{"key":647,"label":648,"href":62},"routes.contact","Contact route",{"key":650,"label":651,"href":59},"routes.updates","Updates route",{"key":653,"label":654,"href":655},"routes.migrationError","Migration error route","\u002Fmigration\u002Ferror",{"key":657,"label":658,"href":659},"routes.authenticated","Post-login authenticated route","\u002Fauth\u002Fsigned-in",{"key":661,"label":662,"href":663},"routes.signed-out","Signed out route","\u002Fauth\u002Fsigned-out",{"key":665,"label":666,"href":667},"routes.authentication-error","Authentication error route","\u002Fauth\u002Ferror",{},"data\u002Fshared\u002Furls","2AvfSWI6bZACH5Kr1ZS3txMLjteRjCjbDR_ul1uRZ9Q",{"id":672,"title":673,"author":674,"authorPhoto":675,"authorPhotoAlt":676,"authorSlug":677,"body":678,"buttonText":1319,"buttonUrl":1320,"category":1321,"date":1322,"description":1323,"draft":1324,"editorsPick":1324,"extension":1325,"featured":32,"image":1326,"imageAlt":676,"meta":1327,"navigation":32,"ogImage":1328,"ogImageAlt":676,"path":1329,"seo":1330,"stem":1331,"tags":676,"__hash__":1332},"blog\u002Fblog\u002Fproduction-ready-productivity-app.md","Building a daily productivity app with Pieces — Part 4: polish & production ready","Bishoy Hany","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui\u002Fauthor.jpeg",null,"bishoy-hany",{"type":679,"value":680,"toc":1285},"minimark",[681,685,715,720,723,757,760,764,769,772,783,787,790,798,808,811,831,835,842,848,854,861,865,876,882,885,888,891,894,897,901,912,918,921,925,928,934,937,941,947,950,965,969,972,976,982,985,988,991,994,997,1001,1004,1010,1013,1019,1022,1036,1039,1043,1046,1051,1057,1061,1068,1074,1077,1081,1087,1091,1097,1101,1104,1110,1113,1117,1123,1126,1132,1138,1142,1148,1154,1157,1201,1204,1208,1211,1216,1220,1226,1230,1236,1240,1243,1249,1252,1258,1261,1265,1271,1277],[682,683,684],"p",{},"Welcome to Part 4! We've come a long way:",[686,687,688,699,707],"ul",{},[689,690,691,698],"li",{},[692,693,697],"a",{"href":694,"rel":695},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fbuilding-daily-standup-generator-with-pieces-api-sdk",[696],"nofollow","Part 1:"," We built a real-time sync with Pieces OS",[689,700,701,706],{},[692,702,705],{"href":703,"rel":704},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fbuilding-pieces-productivity-app-with-gemini-ai",[696],"Part 2",": Then we added AI insights with Gemini",[689,708,709,714],{},[692,710,713],{"href":711,"rel":712},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui",[696],"Part 3",": Lastly, we created a beautiful Flutter UI",[716,717,719],"h2",{"id":718},"the-problems-were-solving","The problems we're solving",[682,721,722],{},"After Part 3, we had a working app, but:",[724,725,726,733,739,745,751],"ol",{},[689,727,728,732],{},[729,730,731],"strong",{},"Every restart regenerates recaps"," - Wastes API calls and time",[689,734,735,738],{},[729,736,737],{},"Errors just fail silently"," - Bad UX",[689,740,741,744],{},[729,742,743],{},"Can't regenerate a recap"," - Stuck with what you got",[689,746,747,750],{},[729,748,749],{},"No tests"," - Can't confidently make changes",[689,752,753,756],{},[729,754,755],{},"No CI\u002FCD"," - Manual testing every time",[682,758,759],{},"Let's fix all of these!",[716,761,763],{"id":762},"feature-1-persistent-caching-with-hive","Feature 1: persistent caching with Hive",[765,766,768],"h3",{"id":767},"the-problem","The problem",[682,770,771],{},"When you close the app, all those AI-generated recaps? Gone. Next time you open it, we regenerate everything. That's:",[686,773,774,777,780],{},[689,775,776],{},"Slow (3+ seconds per day)",[689,778,779],{},"Expensive (API calls cost money)",[689,781,782],{},"Wasteful (Same data, different day)",[765,784,786],{"id":785},"solution-persistent-caching-with-hive","Solution: persistent caching with Hive",[682,788,789],{},"Hive is a lightweight, fast NoSQL database for Flutter. Perfect for caching our recaps!",[682,791,792,793,797],{},"First, we need to update our model file ",[794,795,796],"code",{},"lib\u002Fmodels\u002Fdaily_recap_models.dart"," with Hive annotations:",[799,800,805],"pre",{"className":801,"code":803,"language":804},[802],"language-text","\u002F\u002F Place the import & part at the top of the Dart file\n\nimport 'package:hive\u002Fhive.dart';\n\npart 'daily_recap_models.g.dart';  \u002F\u002F Generated file\n\n\u002F\u002F Replace existing 'class DailyRecapData'. Stopping at the factory constructor.\n\n@HiveType(typeId: 0)\nclass DailyRecapData extends HiveObject {\n  @HiveField(0)\n  final DateTime date;\n\n  @HiveField(1)\n  final String summary;\n\n  @HiveField(2)\n  final List\u003CString> people;\n\n  @HiveField(3)\n  final List\u003CProjectData> projects;\n\n  @HiveField(4)\n  final List\u003CString> reminders;\n\n  @HiveField(5)\n  final List\u003CString> notes;\n\nDailyRecapData({\n    required this.date,\n    required this.summary,\n    required this.people,\n    required this.projects,\n    required this.reminders,\n    required this.notes,\n  });\n  \u002F\u002F Constructor, fromJson, toJson...\n}\n\n\u002F\u002F Replace existing 'class ProjectData'\n\n@HiveType(typeId: 1)\nclass ProjectData {\n  @HiveField(0)\n  final String name;\n\n  @HiveField(1)\n  final String description;\n\n  @HiveField(2)\n  final ProjectStatus status;\n  \n    ProjectData({\n    required this.name,\n    required this.description,\n    required this.status,\n  });\n  \n  \n}\n\n\u002F\u002F Replace the existing 'enum ProjectStatus'\n\n@HiveType(typeId: 2)\nenum ProjectStatus {\n  @HiveField(0)\n  completed,\n\n  @HiveField(1)\n  inProgress,\n\n  @HiveField(2)\n  notStarted,\n}\n","text",[794,806,803],{"__ignoreMap":807},"",[682,809,810],{},"Keep in mind:",[686,812,813,819,825],{},[689,814,815,818],{},[794,816,817],{},"@HiveType(typeId: X)"," - Unique ID for each type",[689,820,821,824],{},[794,822,823],{},"@HiveField(X)"," - Field index for serialization",[689,826,827,830],{},[794,828,829],{},"extends HiveObject"," - Makes it a Hive object",[765,832,834],{"id":833},"adding-hive-dependencies","Adding Hive Dependencies",[682,836,837,838,841],{},"Now we need to add Hive to our ",[794,839,840],{},"pubspec.yaml",":",[799,843,846],{"className":844,"code":845,"language":804},[802],"dependencies:\n  hive: ^2.2.3\n  hive_flutter: ^1.1.0\n\ndev_dependencies:\n  hive_generator: ^2.0.1\n  build_runner: ^2.4.13\n",[794,847,845],{"__ignoreMap":807},[799,849,852],{"className":850,"code":851,"language":804},[802],"flutter pub get && flutter pub run build_runner build --delete-conflicting-outputs\n",[794,853,851],{"__ignoreMap":807},[682,855,856,857,860],{},"This generates ",[794,858,859],{},"daily_recap_models.g.dart"," with type-safe adapters!",[765,862,864],{"id":863},"updating-dailyrecapservice","Updating DailyRecapService",[682,866,867,868,871,872,875],{},"Now we need to update our ",[794,869,870],{},"DailyRecapService"," in ",[794,873,874],{},"daily_recap_service.dart"," to use Hive. Add these new methods:",[799,877,880],{"className":878,"code":879,"language":804},[802],"\u002F\u002F Add to top of file, where imports (if any) are\n\nimport 'package:hive\u002Fhive.dart';\nimport 'package:hive_flutter\u002Fhive_flutter.dart';\n\n\u002F\u002F Replace the DailyRecapService class\n\nclass DailyRecapService {\n  final GenerativeModel _model;\n  late final Box\u003CDailyRecapData> _cacheBox;  \u002F\u002F NEW: Hive cache box\n\n   DailyRecapService({required String apiKey})\n    : _model = GenerativeModel(\n        model: 'gemini-2.5-pro',\n        apiKey: apiKey,\n      );\n\n  \u002F\u002F\u002F NEW: Initialize Hive and open the cache box\n  Future\u003Cvoid> initialize() async {\n    await Hive.initFlutter();\n\n    \u002F\u002F Register adapters\n    if (!Hive.isAdapterRegistered(0)) {\n      Hive.registerAdapter(DailyRecapDataAdapter());\n    }\n    if (!Hive.isAdapterRegistered(1)) {\n      Hive.registerAdapter(ProjectDataAdapter());\n    }\n    if (!Hive.isAdapterRegistered(2)) {\n      Hive.registerAdapter(ProjectStatusAdapter());\n    }\n    \n    \u002F\u002F Open typed box\n    _cacheBox = await Hive.openBox\u003CDailyRecapData>('daily_recaps_cache');\n  }\n\n  DailyRecapData? getCachedRecap(DateTime date) {\n    final key = _dateToKey(date);\n    return _cacheBox.get(key);  \u002F\u002F Returns DailyRecapData directly!\n  }\n\n  Future\u003Cvoid> _saveToCache(DateTime date, DailyRecapData recap) async {\n    final key = _dateToKey(date);\n    await _cacheBox.put(key, recap);  \u002F\u002F Stores typed object!\n  }\n String _dateToKey(DateTime date) {\n  return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';\n}\n\n  \u002F\u002F Update the generateDailyRecap function\n\n  Future\u003CDailyRecapData> generateDailyRecap({\n    required DateTime date,\n    required List\u003CSummaryWithContent> summaries,\n    bool forceRegenerate = false, \u002F\u002F Add this field\n  }) async {\n    \u002F\u002F Check cache first (unless forcing regeneration)\n    if (!forceRegenerate) { \u002F\u002F Add this if\n      final cached = getCachedRecap(date);\n      if (cached != null) {\n        \u002F\u002F ignore: avoid_print\n        print('Using cached recap for ${_dateToKey(date)}');\n        return cached;\n      }\n    }\n\n    if (summaries.isEmpty) {\n      return DailyRecapData.empty(date);\n    }\n\n    \u002F\u002F Build context from summaries\n    final context = _buildSummariesContext(summaries);\n\n    \u002F\u002F Craft the prompt\n    final prompt = _buildPrompt(date, context);\n\n    try {\n      final response = await _model.generateContent([Content.text(prompt)]);\n      \u002F\u002F ignore: avoid_print\n      print(\"Gemini response received. ${response.text}\");\n      final jsonText = response.text ?? '{}';\n\n      \u002F\u002F Parse the JSON response\n      final data = jsonDecode(jsonText) as Map\u003CString, dynamic>;\n\n      final recap = DailyRecapData.fromJson(date, data);\n\n      \u002F\u002F Save to cache Add this as well\n      await _saveToCache(date, recap);\n\n      return recap;\n    } catch (e) {\n      \u002F\u002F ignore: avoid_print\n      print('Error generating daily recap: $e');\n      rethrow; \u002F\u002F Throw error so UI can handle it\n    }\n  }\n",[794,881,879],{"__ignoreMap":807},[682,883,884],{},"Results:",[682,886,887],{},"✅ App restart: Instant load (no API calls!)",[682,889,890],{},"✅ Type safe: No runtime errors",[682,892,893],{},"✅ Persistent: Survives app closes",[682,895,896],{},"✅ Fast: Disk read vs network call",[765,898,900],{"id":899},"updating-the-app-initialization","Updating the app initialization",[682,902,903,904,907,908,911],{},"Now in your ",[794,905,906],{},"main.dart",", update the service initialization to call the new ",[794,909,910],{},"initialize()"," method:",[799,913,916],{"className":914,"code":915,"language":804},[802],"\u002F\u002F Replace _intitializeServices()\n\nFuture\u003Cvoid> _initializeServices() async {\n  setState(() => _loadingState = LoadingState.loading);\n\n  try {\n    \u002F\u002F Initialize Pieces OS\n    await _piecesService.initialize();\n    await _piecesService.waitForInitialSync();\n\n    \u002F\u002F Initialize Gemini with Hive caching\n    const apiKey = String.fromEnvironment('GEMINI_API_KEY');\n    if (apiKey.isNotEmpty) {\n      _recapService = DailyRecapService(apiKey: apiKey);\n      await _recapService!.initialize();  \u002F\u002F Initialize Hive\n    }\n\n    \u002F\u002F Load days and recaps...\n    _loadedDays = _piecesService.getDaysWithSummaries();\n    await _loadRecapsForVisibleDays();\n\n    setState(() => _loadingState = LoadingState.healthy);\n  } catch (e) {\n    setState(() => _loadingState = LoadingState.somethingWentWrong);\n  }\n}\n",[794,917,915],{"__ignoreMap":807},[682,919,920],{},"Now, when you restart the app, cached recaps load instantly!",[716,922,924],{"id":923},"feature-2-regenerate-button","Feature 2: regenerate button",[682,926,927],{},"Sometimes you want a fresh recap (maybe you added more work, or the AI messed up). Easy fix - add a refresh button!",[799,929,932],{"className":930,"code":931,"language":804},[802],"\u002F\u002F Inside DailyRecapCard's build() method, replace the Date Header section (around line 333)\n\u002F\u002F Date Header with refresh button\n\u002F\u002F This is within children[ ... ] stopping at 'const SizedBox(height: 16)'\n\nRow(\n  mainAxisAlignment: MainAxisAlignment.spaceBetween,\n  children: [\n    Column(\n      crossAxisAlignment: CrossAxisAlignment.start,\n      children: [\n        Text(dateFormatter(recap.date)),\n        Text(DateFormat('MMMM d, y').format(recap.date)),\n      ],\n    ),\n    IconButton(\n      icon: const Icon(Icons.refresh),\n      tooltip: 'Regenerate recap',\n      onPressed: onRegenerate,  \u002F\u002F Callback!\n      color: Colors.blue.shade700,\n    ),\n  ],\n)\n",[794,933,931],{"__ignoreMap":807},[682,935,936],{},"Small, unobtrusive, but super useful!",[765,938,940],{"id":939},"the-regeneration-logic","The regeneration logic",[799,942,945],{"className":943,"code":944,"language":804},[802],"\u002F\u002F Add this method after _loadMore() inside _MyHomePageState class in main.dart\n\nFuture\u003Cvoid> _regenerateRecap(DateTime day) async {\n  \u002F\u002F Mark as regenerating\n  setState(() => _regenerating.add(day));\n\n  try {\n    final summariesWithContent =\n        await _piecesService.getSummariesWithContentForDay(day);\n\n    final recap = await _recapService!.generateDailyRecap(\n      date: day,\n      summaries: summariesWithContent,\n      forceRegenerate: true,  \u002F\u002F Bypass cache!\n    );\n\n    setState(() {\n      _recapsCache[day] = recap;\n      _regenerating.remove(day);\n    });\n  } catch (e) {\n    setState(() {\n      _errorCache[day] = e.toString();\n      _regenerating.remove(day);\n    });\n  }\n}\n",[794,946,944],{"__ignoreMap":807},[682,948,949],{},"We track state:",[686,951,952,959,962],{},[689,953,954,955,958],{},"Add to ",[794,956,957],{},"_regenerating"," set → Show loading card",[689,960,961],{},"Success → Update cache and remove from set",[689,963,964],{},"Error → Add to error cache",[716,966,968],{"id":967},"feature-3-beautiful-error-handling","Feature 3: beautiful error handling",[682,970,971],{},"When Gemini fails (rate limits, network issues, invalid API key), we need good UX!",[765,973,975],{"id":974},"the-error-card","The error card",[799,977,980],{"className":978,"code":979,"language":804},[802],"\u002F\u002F Add this method after _buildLoadingCard() inside _MyHomePageState class in main.dart\n\nWidget _buildErrorCard(DateTime date, String error) {\n  return Card(\n    elevation: 4,\n    color: Colors.red.shade50,  \u002F\u002F Light red background\n    child: Padding(\n      padding: const EdgeInsets.all(16.0),\n      child: Column(\n        crossAxisAlignment: CrossAxisAlignment.start,\n        children: [\n          \u002F\u002F Header with error icon\n          Row(\n            children: [\n              Icon(Icons.error_outline, color: Colors.red.shade700),\n              const SizedBox(width: 8),\n              Text(\n                dateFormatter(date),\n                style: Theme.of(context).textTheme.headlineSmall,\n              ),\n            ],\n          ),\n\n          \u002F\u002F Error message\n          Card(\n            color: Colors.white,\n            child: Padding(\n              padding: const EdgeInsets.all(16.0),\n              child: Column(\n                children: [\n                  Icon(Icons.warning, color: Colors.orange.shade700),\n                  const SizedBox(height: 8),\n                  const Text(\n                    'Failed to generate recap',\n                    style: TextStyle(fontWeight: FontWeight.bold),\n                  ),\n                  const SizedBox(height: 8),\n                  Text(\n                    'There was an issue generating the AI recap for this day.',\n                    style: TextStyle(color: Colors.grey.shade700),\n                  ),\n                  const SizedBox(height: 16),\n                  \n                  \u002F\u002F Try again button!\n                  ElevatedButton.icon(\n                    onPressed: () => _regenerateRecap(date),\n                    icon: const Icon(Icons.refresh),\n                    label: const Text('Try Again'),\n                    style: ElevatedButton.styleFrom(\n                      backgroundColor: Colors.orange,\n                      foregroundColor: Colors.white,\n                    ),\n                  ),\n                ],\n              ),\n            ),\n          ),\n        ],\n      ),\n    ),\n  );\n}\n",[794,981,979],{"__ignoreMap":807},[682,983,984],{},"Users see:",[682,986,987],{},"❌ Clear error indication (red card)",[682,989,990],{},"⚠️ Warning icon",[682,992,993],{},"🔄 \"Try Again\" button (orange, action-oriented)",[682,995,996],{},"Much better than a crash or blank card!",[765,998,1000],{"id":999},"state-management","State management",[682,1002,1003],{},"We track three states per card:",[799,1005,1008],{"className":1006,"code":1007,"language":804},[802],"\u002F\u002F No action needed :)\n\nfinal Map\u003CDateTime, DailyRecapData> _recapsCache = {};  \u002F\u002F Success\nfinal Map\u003CDateTime, String> _errorCache = {};           \u002F\u002F Error\nfinal Set\u003CDateTime> _regenerating = {};                 \u002F\u002F Loading\n",[794,1009,1007],{"__ignoreMap":807},[682,1011,1012],{},"Then in the UI:",[799,1014,1017],{"className":1015,"code":1016,"language":804},[802],"\u002F\u002F No action needed :)\n\nchildren: visibleDays.map((day) {\n  final recap = _recapsCache[day];\n  final error = _errorCache[day];\n  final isRegenerating = _regenerating.contains(day);\n\n  return SizedBox(\n    width: 400,\n    child: isRegenerating\n        ? _buildLoadingCard(day, isRegenerating: true)\n        : error != null\n            ? _buildErrorCard(day, error)\n            : recap != null\n                ? DailyRecapCard(recap: recap, onRegenerate: () =>\n                  _regenerateRecap(day),\n                ))\n                : _buildLoadingCard(day),\n  );\n}).toList(),\n",[794,1018,1016],{"__ignoreMap":807},[682,1020,1021],{},"Priority:",[724,1023,1024,1027,1030,1033],{},[689,1025,1026],{},"Is it regenerating? → Show loading with \"Regenerating...\" text",[689,1028,1029],{},"Is there an error? → Show error card with retry button",[689,1031,1032],{},"Is it loaded? → Show recap card with regenerate button",[689,1034,1035],{},"Otherwise → Show loading card",[682,1037,1038],{},"Clean and predictable!",[716,1040,1042],{"id":1041},"feature-4-automated-tests","Feature 4: automated tests",[682,1044,1045],{},"Writing tests for Flutter apps with external services is tricky. Here's what I did:",[682,1047,1048,1049],{},"Start by adding our required dependencies to you ",[794,1050,840],{},[799,1052,1055],{"className":1053,"code":1054,"language":804},[802],"dev_dependencies:\n  flutter_test:\n    sdk: flutter\n",[794,1056,1054],{"__ignoreMap":807},[765,1058,1060],{"id":1059},"data-model-tests","Data model tests",[682,1062,1063,1064,1067],{},"Now, let’s test the core data structures. In a new Dart file ",[794,1065,1066],{},"test\u002Fdaily_recap_models_test.dart",", place our test:",[799,1069,1072],{"className":1070,"code":1071,"language":804},[802],"import 'package:flutter_test\u002Fflutter_test.dart';\nimport 'package:blog_walkthrough\u002Fmodels\u002Fdaily_recap_models.dart';\n\nvoid main() {\n  test('fromJson creates valid DailyRecapData object', () {\n    final date = DateTime(2025, 11, 4);\n    final json = {\n      'summary': 'Test summary',\n      'people': ['Alice', 'Bob'],\n      'projects': [\n        {\n          'name': 'Project A',\n          'description': 'Test project',\n          'status': 'completed',\n        }\n      ],\n      'reminders': ['Test reminder'],\n      'notes': ['Test note'],\n    };\n\n    final recap = DailyRecapData.fromJson(date, json);\n\n    expect(recap.summary, 'Test summary');\n    expect(recap.people, ['Alice', 'Bob']);\n    expect(recap.projects.first.status, ProjectStatus.completed);\n  });\n}\n",[794,1073,1071],{"__ignoreMap":807},[682,1075,1076],{},"This code tests all the parsing logic!",[765,1078,1080],{"id":1079},"project-status-parsing","Project status parsing",[799,1082,1085],{"className":1083,"code":1084,"language":804},[802],"import 'package:flutter_test\u002Fflutter_test.dart';\nimport 'package:blog_walkthrough\u002Fmodels\u002Fdaily_recap_models.dart';\n\nvoid main() {\n  group('ProjectData', () {\n    test('fromJson handles all status types', () {\n      expect(\n        ProjectData.fromJson({\n          'name': 'Test Project',\n          'description': 'Test description',\n          'status': 'completed',\n        }).status,\n        ProjectStatus.completed,\n      );\n\n      expect(\n        ProjectData.fromJson({\n          'name': 'Test Project',\n          'description': 'Test description',\n          'status': 'in_progress',\n        }).status,\n        ProjectStatus.inProgress,\n      );\n\n      expect(\n        ProjectData.fromJson({\n          'name': 'Test Project',\n          'description': 'Test description',\n          'status': 'invalid',\n        }).status,\n        ProjectStatus.notStarted,  \u002F\u002F Default fallback\n      );\n    });\n  });\n}\n",[794,1086,1084],{"__ignoreMap":807},[765,1088,1090],{"id":1089},"logic-tests-no-external-dependencies","Logic tests (no external dependencies)",[799,1092,1095],{"className":1093,"code":1094,"language":804},[802],"import 'package:flutter_test\u002Fflutter_test.dart';\n\nvoid main() {\n  test('date normalization to midnight works correctly', () {\n    final date1 = DateTime(2025, 11, 4, 14, 30);  \u002F\u002F 2:30 PM\n    final date2 = DateTime(2025, 11, 4, 0, 0);    \u002F\u002F Midnight\n\n    final normalized1 = DateTime(date1.year, date1.month, date1.day);\n    final normalized2 = DateTime(date2.year, date2.month, date2.day);\n\n    expect(normalized1, normalized2);\n    expect(normalized1.hour, 0);\n  });\n}\n",[794,1096,1094],{"__ignoreMap":807},[765,1098,1100],{"id":1099},"skipping-integration-tests","Skipping integration tests",[682,1102,1103],{},"Tests that need Pieces OS running:",[799,1105,1108],{"className":1106,"code":1107,"language":804},[802],"import 'package:flutter_test\u002Fflutter_test.dart';\n\u002F\u002F import 'package:blog_walkthrough\u002Fservices\u002Fpieces_os_service.dart';\n\nvoid main() {\n  group('PiecesOSService', () {\n    test('getDaysWithSummaries requires Pieces OS', () {\n      \u002F\u002F This test requires a running Pieces OS instance\n      \u002F\u002F It's covered by integration tests\n    }, skip: 'Requires Pieces OS connection');\n  });\n}\n",[794,1109,1107],{"__ignoreMap":807},[682,1111,1112],{},"Clean and honest!",[765,1114,1116],{"id":1115},"running-the-tests","Running the tests",[799,1118,1121],{"className":1119,"code":1120,"language":804},[802],"flutter test\n",[794,1122,1120],{"__ignoreMap":807},[682,1124,1125],{},"Output:",[799,1127,1130],{"className":1128,"code":1129,"language":804},[802],"00:01 +11 ~1: All tests passed!\n",[794,1131,1129],{"__ignoreMap":807},[682,1133,1134,1137],{},[729,1135,1136],{},"11 tests passed, 1 skipped!"," ✅",[716,1139,1141],{"id":1140},"feature-5-cicd-with-github-actions","Feature 5: CI\u002FCD with GitHub Actions",[682,1143,1144,1145,841],{},"Automated testing on every push\u002FPR! Created ",[794,1146,1147],{},".github\u002Fworkflows\u002Ftest.yml",[799,1149,1152],{"className":1150,"code":1151,"language":804},[802],"name: Run Tests\n\non:\n  push:\n    branches: [ main ]\n  pull_request:\n    branches: [ main ]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions\u002Fcheckout@v4\n\n      - name: Setup Flutter\n        uses: subosito\u002Fflutter-action@v2\n        with:\n          flutter-version: '3.24.0'\n          channel: 'stable'\n\n      - name: Install dependencies\n        run: flutter pub get\n\n      - name: Verify formatting\n        run: dart format --set-exit-if-changed lib test\n\n      - name: Analyze code\n        run: flutter analyze\n\n      - name: Run tests\n        run: flutter test\n\n  build:\n    runs-on: macos-latest\n    needs: test\n\n    steps:\n      - name: Checkout code\n        uses: actions\u002Fcheckout@v4\n\n      - name: Setup Flutter\n        uses: subosito\u002Fflutter-action@v2\n\n      - name: Install dependencies\n        run: flutter pub get\n\n      - name: Build macOS app\n        run: flutter build macos --release\n\n      - name: Upload macOS artifact\n        uses: actions\u002Fupload-artifact@v3\n        with:\n          name: macos-app\n          path: build\u002Fmacos\u002FBuild\u002FProducts\u002FRelease\u002Fdaily_recap_app.app\n",[794,1153,1151],{"__ignoreMap":807},[682,1155,1156],{},"What it does:",[724,1158,1159,1165,1171,1177,1183,1189,1195],{},[689,1160,1161,1164],{},[729,1162,1163],{},"On every push\u002FPR",": Runs automatically",[689,1166,1167,1170],{},[729,1168,1169],{},"Check formatting",": Ensures code style",[689,1172,1173,1176],{},[729,1174,1175],{},"Analyze",": Catches potential issues",[689,1178,1179,1182],{},[729,1180,1181],{},"Run tests",": All 11 tests must pass",[689,1184,1185,1188],{},[729,1186,1187],{},"Generate coverage",": See what's tested",[689,1190,1191,1194],{},[729,1192,1193],{},"Build macOS app",": Verify it compiles",[689,1196,1197,1200],{},[729,1198,1199],{},"Upload artifact",": Downloadable .app file",[682,1202,1203],{},"Now you can't merge broken code!",[716,1205,1207],{"id":1206},"running-everything","Running everything",[765,1209,1181],{"id":1210},"run-tests",[799,1212,1214],{"className":1213,"code":1120,"language":804},[802],[794,1215,1120],{"__ignoreMap":807},[765,1217,1219],{"id":1218},"generate-hive-adapters-if-models-change","Generate Hive adapters (if models change)",[799,1221,1224],{"className":1222,"code":1223,"language":804},[802],"flutter pub run build_runner build --delete-conflicting-outputs\n",[794,1225,1223],{"__ignoreMap":807},[765,1227,1229],{"id":1228},"run-the-app","Run the app",[799,1231,1234],{"className":1232,"code":1233,"language":804},[802],"flutter run -d macos --dart-define=GEMINI_API_KEY=your-key\n",[794,1235,1233],{"__ignoreMap":807},[765,1237,1239],{"id":1238},"see-it-in-action","See it in Action",[682,1241,1242],{},"First run:",[799,1244,1247],{"className":1245,"code":1246,"language":804},[802],"Connecting to Pieces OS... (5 seconds)\nGenerating recap with AI... (3 seconds per card)\n",[794,1248,1246],{"__ignoreMap":807},[682,1250,1251],{},"Close and reopen:",[799,1253,1256],{"className":1254,"code":1255,"language":804},[802],"Connecting to Pieces OS... (5 seconds)\nUsing cached recap for 2025-11-06  ← Instant!\nUsing cached recap for 2025-11-05  ← Instant!\nUsing cached recap for 2025-11-04  ← Instant!\n",[794,1257,1255],{"__ignoreMap":807},[682,1259,1260],{},"So much faster!",[716,1262,1264],{"id":1263},"final-project-structure","Final project structure",[799,1266,1269],{"className":1267,"code":1268,"language":804},[802],"daily_recap_app\u002F\n├── lib\u002F\n│   ├── main.dart - UI with error handling & regenerate\n│   ├── models\u002F\n│   │   ├── daily_recap_models.dart - Hive models\n│   │   └── daily_recap_models.g.dart (generated) - Type adapters\n│   └── services\u002F\n│       ├── pieces_os_service.dart\n│       └── daily_recap_service.dart - With Hive caching\n├── test\u002F\n│   ├── daily_recap_service_test.dart - 9 tests\n│   └── pieces_os_service_test.dart - 3 tests\n├── .github\u002F\n│   └── workflows\u002F\n│       └── test.yml - CI\u002FCD pipeline\n└── pubspec.yaml\n",[794,1270,1268],{"__ignoreMap":807},[682,1272,1273],{},[1274,1275,1276],"em",{},"If this helped you or you built something cool with it, I'd love to hear about it!",[682,1278,1279,1284],{},[692,1280,1283],{"href":1281,"rel":1282},"https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fblog-dart-daily-stand-up-generator",[696],"Reference GitHub"," to view the full project.",{"title":807,"searchDepth":1286,"depth":1286,"links":1287},2,[1288,1289,1297,1300,1304,1311,1312,1318],{"id":718,"depth":1286,"text":719},{"id":762,"depth":1286,"text":763,"children":1290},[1291,1293,1294,1295,1296],{"id":767,"depth":1292,"text":768},3,{"id":785,"depth":1292,"text":786},{"id":833,"depth":1292,"text":834},{"id":863,"depth":1292,"text":864},{"id":899,"depth":1292,"text":900},{"id":923,"depth":1286,"text":924,"children":1298},[1299],{"id":939,"depth":1292,"text":940},{"id":967,"depth":1286,"text":968,"children":1301},[1302,1303],{"id":974,"depth":1292,"text":975},{"id":999,"depth":1292,"text":1000},{"id":1041,"depth":1286,"text":1042,"children":1305},[1306,1307,1308,1309,1310],{"id":1059,"depth":1292,"text":1060},{"id":1079,"depth":1292,"text":1080},{"id":1089,"depth":1292,"text":1090},{"id":1099,"depth":1292,"text":1100},{"id":1115,"depth":1292,"text":1116},{"id":1140,"depth":1286,"text":1141},{"id":1206,"depth":1286,"text":1207,"children":1313},[1314,1315,1316,1317],{"id":1210,"depth":1292,"text":1181},{"id":1218,"depth":1292,"text":1219},{"id":1228,"depth":1292,"text":1229},{"id":1238,"depth":1292,"text":1239},{"id":1263,"depth":1286,"text":1264},"Try Pieces","https:\u002F\u002Fpieces.app\u002F","Productivity","2025-12-09T00:00:00.000Z","Build a daily productivity app with Pieces in Part 4 of the series: polish the UX, tighten performance, fix edge cases, and make your app production-ready with deployment best practices.",false,"md","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fproduction-ready-productivity-app\u002Fhero.png",{},"https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fopen-graph\u002Fblog\u002Fproduction-ready-productivity-app.png","\u002Fblog\u002Fproduction-ready-productivity-app",{"title":673,"description":1323},"blog\u002Fproduction-ready-productivity-app","2ZiskSWkxEWzVkS9TN4kwtoyRNgvMQs_miqU7ithC08",{"id":1334,"title":674,"body":1335,"description":1339,"draft":1324,"extension":1325,"meta":1342,"navigation":32,"path":1343,"photo":1344,"photoAlt":676,"seo":1345,"stem":1346,"__hash__":1347},"authors\u002Fauthors\u002Fbishoy-hany.md",{"type":679,"value":1336,"toc":1340},[1337],[682,1338,1339],{},"Bishoy Hany is a Software Engineer at Pieces, where he plays a key role in building developer-first AI experiences that run locally and respect user privacy. With a strong focus on performance, reliability, and intelligent automation, Bishoy works across the full stack to help shape the future of long-term memory and contextual AI.",{"title":807,"searchDepth":1286,"depth":1286,"links":1341},[],{},"\u002Fauthors\u002Fbishoy-hany","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fauthors\u002Fbishoy-hany.jpg",{"title":674,"description":1339},"authors\u002Fbishoy-hany","_m1q8mqNk1I4j1V0_YJSK0zuMS48DBT4Nqw9e3PZjMM",{"left":1349,"top":1349,"width":1350,"height":1350,"rotate":1349,"vFlip":1324,"hFlip":1324,"body":1351},0,24,"\u003Cpath fill=\"currentColor\" d=\"m7.825 13l4.9 4.9q.3.3.288.7t-.313.7q-.3.275-.7.288t-.7-.288l-6.6-6.6q-.15-.15-.213-.325T4.426 12t.063-.375t.212-.325l6.6-6.6q.275-.275.688-.275t.712.275q.3.3.3.713t-.3.712L7.825 11H19q.425 0 .713.288T20 12t-.288.713T19 13z\"\u002F>"]