[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"navigation":3,"url-settings":86,"blog-\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui":663,"blog-author-\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui":1448,"i-material-symbols:arrow-back-rounded":1463},{"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},"Sign in","url:portal.home",{"label":79,"href":62},"Get in touch",{"label":81,"href":82},"Download","url:routes.downloads",{},"data\u002Fshared\u002Fnavigation","VdirGIa8cUm6xLprbBeTKcw914JfyJjWfF-9hSVOX5Y",{"id":87,"extension":5,"links":88,"meta":660,"stem":661,"__hash__":662},"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,295,299,303,306,310,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,453,457,461,465,469,473,477,480,483,486,489,493,497,500,504,508,512,516,520,524,528,532,536,540,544,548,551,555,559,563,567,571,574,577,580,584,587,591,595,599,603,606,610,614,617,620,623,627,631,635,638,641,644,648,652,656],{"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.piecesOS","PiecesOS manual installation","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fmanual-installation",{"key":209,"label":210,"href":211},"extensions.chrome","Chrome extension","https:\u002F\u002Fchrome.google.com\u002Fwebstore\u002Fdetail\u002Fpieces-save-code-snippets\u002Figbgibhbfonhmjlechmeefimncpekepm",{"key":213,"label":214,"href":215},"extensions.firefox","Firefox add-on","https:\u002F\u002Faddons.mozilla.org\u002Fen-US\u002Ffirefox\u002Faddon\u002Fpieces-save-code-from-the-web\u002F",{"key":217,"label":218,"href":219},"extensions.edge","Edge add-on","https:\u002F\u002Fmicrosoftedge.microsoft.com\u002Faddons\u002Fdetail\u002Fpieces-save-code-snippet\u002Fhglfimcdgonaeeobjckfdabcldfidmim",{"key":221,"label":222,"href":223},"extensions.vscode","VS Code extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.pieces-vscode",{"key":225,"label":226,"href":227},"extensions.visualStudio","Visual Studio extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.PiecesVisualStudio",{"key":229,"label":230,"href":231},"extensions.jetbrains","JetBrains plugin","https:\u002F\u002Fplugins.jetbrains.com\u002Fplugin\u002F17328-pieces--save-search-share--reuse-code-snippets",{"key":233,"label":234,"href":235},"extensions.obsidian","Obsidian plugin","https:\u002F\u002Fobsidian.md\u002Fplugins?id=pieces-for-developers",{"key":237,"label":238,"href":239},"extensions.sublime","Sublime package","https:\u002F\u002Fpackagecontrol.io\u002Fpackages\u002FPieces",{"key":241,"label":242,"href":243},"extensions.neovim","Neovim plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neo_vim",{"key":245,"label":246,"href":247},"extensions.jupyterlab","JupyterLab plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fjupyterlab-pieces",{"key":249,"label":250,"href":251},"extensions.cli","Pieces CLI","https:\u002F\u002Fpypi.org\u002Fproject\u002Fpieces-cli\u002F",{"key":253,"label":254,"href":255},"docs.home","Documentation home","https:\u002F\u002Fdocs.pieces.app",{"key":257,"label":258,"href":259},"docs.getStarted","Get started docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces",{"key":261,"label":262,"href":263},"docs.api","API docs","https:\u002F\u002Fdocs.pieces.app\u002Fapi",{"key":265,"label":266,"href":267},"docs.desktop.overview","Desktop overview","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop",{"key":269,"label":270,"href":271},"docs.desktop.onboarding","Desktop onboarding","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fonboarding",{"key":273,"label":274,"href":275},"docs.desktop.timeline","Desktop timeline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Ftimeline",{"key":277,"label":278,"href":279},"docs.desktop.summaries","Desktop summaries docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fsingle-click-summaries",{"key":281,"label":282,"href":283},"docs.desktop.search","Desktop conversational search docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconversational-search",{"key":285,"label":286,"href":287},"docs.desktop.drive","Desktop drive docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdrive",{"key":289,"label":290,"href":291},"docs.desktop.account","Desktop account settings docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconfiguration\u002Faccount",{"key":293,"label":294,"href":92},"docs.desktop.download","Desktop download docs",{"key":296,"label":297,"href":298},"docs.piecesOS.overview","PiecesOS overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies",{"key":300,"label":301,"href":302},"docs.piecesOS.details","PiecesOS details docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os",{"key":304,"label":305,"href":207},"docs.piecesOS.install","PiecesOS install docs",{"key":307,"label":308,"href":309},"docs.piecesOS.quickMenu","PiecesOS quick menu docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fquick-menu",{"key":311,"label":312,"href":313},"docs.piecesOS.storage","On-device storage docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fon-device-storage",{"key":315,"label":316,"href":317},"docs.piecesOS.troubleshooting","PiecesOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Ftroubleshooting",{"key":319,"label":320,"href":321},"docs.mcp.overview","MCP overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp",{"key":323,"label":324,"href":325},"docs.mcp.cursor","MCP Cursor docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcursor",{"key":327,"label":328,"href":329},"docs.mcp.vscode","MCP VS Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fvs-code",{"key":331,"label":332,"href":333},"docs.mcp.claudeDesktop","MCP Claude Desktop docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-desktop",{"key":335,"label":336,"href":337},"docs.mcp.claudeCode","MCP Claude Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-code",{"key":339,"label":340,"href":341},"docs.mcp.claudeCowork","MCP Claude Cowork docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-cowork",{"key":343,"label":344,"href":345},"docs.mcp.githubCopilot","MCP GitHub Copilot docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgithub-copilot",{"key":347,"label":348,"href":349},"docs.mcp.goose","MCP Goose docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoose",{"key":351,"label":352,"href":353},"docs.mcp.windsurf","MCP Windsurf docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fwindsurf",{"key":355,"label":356,"href":357},"docs.mcp.zed","MCP Zed docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fzed",{"key":359,"label":360,"href":361},"docs.mcp.jetbrains","MCP JetBrains docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fjetbrains-ides",{"key":363,"label":364,"href":365},"docs.mcp.continueDev","MCP Continue docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcontinue-dev",{"key":367,"label":368,"href":369},"docs.mcp.cline","MCP Cline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcline",{"key":371,"label":372,"href":373},"docs.mcp.raycast","MCP Raycast docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fraycast",{"key":375,"label":376,"href":377},"docs.mcp.rovoDevCli","MCP Rovo Dev CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Frovo-dev-cli",{"key":379,"label":380,"href":381},"docs.mcp.openaiCodexCli","MCP OpenAI Codex CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenai-codex-cli",{"key":383,"label":384,"href":385},"docs.mcp.googleGeminiCli","MCP Google Gemini CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoogle-gemini-cli",{"key":387,"label":388,"href":389},"docs.mcp.amazonQ","MCP Amazon Q docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Famazon-q-developer",{"key":391,"label":392,"href":393},"docs.mcp.chatgptDev","MCP ChatGPT Developer Mode docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fchatgpt-developer-mode",{"key":395,"label":396,"href":397},"docs.mcp.openclaw","MCP OpenClaw docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenclaw",{"key":399,"label":400,"href":401},"docs.mcp.mcpRemote","MCP Remote docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fmcp-remote",{"key":403,"label":404,"href":405},"docs.mcp.ngrok","MCP ngrok docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fngrok-setup",{"key":407,"label":408,"href":409},"docs.troubleshooting.macOS","macOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fmacos",{"key":411,"label":412,"href":413},"docs.troubleshooting.windows","Windows troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fwindows",{"key":415,"label":416,"href":417},"docs.troubleshooting.linux","Linux troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Flinux",{"key":419,"label":420,"href":421},"docs.privacy","Privacy and security docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fprivacy-security-your-data",{"key":423,"label":424,"href":425},"docs.support","Support docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fsupport",{"key":427,"label":428,"href":429},"portal.home","Pieces portal","https:\u002F\u002Fportal.pieces.app",{"key":431,"label":432,"href":433},"site.home","Website home","https:\u002F\u002Fpieces.app",{"key":435,"label":436,"href":437},"site.about","About page","https:\u002F\u002Fpieces.app\u002Fabout",{"key":439,"label":440,"href":441},"site.features","Features page","https:\u002F\u002Fpieces.app\u002Ffeatures",{"key":443,"label":444,"href":445},"site.plugins","Plugins page","https:\u002F\u002Fpieces.app\u002Fplugins",{"key":447,"label":448,"href":449},"site.contact","Contact page","https:\u002F\u002Fpieces.app\u002Fcontact",{"key":451,"label":58,"href":452},"site.updates","https:\u002F\u002Fpieces.app\u002Fupdates",{"key":454,"label":455,"href":456},"site.news","News","https:\u002F\u002Fpieces.app\u002Fnews",{"key":458,"label":459,"href":460},"site.events","Community events","https:\u002F\u002Fpieces.app\u002Fcommunity\u002Fevents",{"key":462,"label":463,"href":464},"site.userStories","User stories","https:\u002F\u002Fpieces.app\u002Fuser-stories",{"key":466,"label":467,"href":468},"site.academy","Academy","https:\u002F\u002Fpieces.app\u002Flearn\u002Facademy",{"key":470,"label":471,"href":472},"site.support","Website support","https:\u002F\u002Fpieces.app\u002Fsupport",{"key":474,"label":475,"href":476},"site.standup","Standup","https:\u002F\u002Fpieces.app\u002Fstandup",{"key":478,"label":34,"href":479},"site.blog","https:\u002F\u002Fcode.pieces.app\u002Fblog",{"key":481,"label":43,"href":482},"social.discord","https:\u002F\u002Fdiscord.gg\u002Fgetpieces",{"key":484,"label":46,"href":485},"social.x","https:\u002F\u002Fx.com\u002Fgetpieces",{"key":487,"label":488,"href":485},"social.twitter","Twitter",{"key":490,"label":491,"href":492},"social.instagram","Instagram","https:\u002F\u002Fwww.instagram.com\u002Fgetpieces\u002F",{"key":494,"label":495,"href":496},"social.tiktok","TikTok","https:\u002F\u002Fwww.tiktok.com\u002F@getpieces",{"key":498,"label":49,"href":499},"social.linkedin","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fgetpieces\u002F",{"key":501,"label":502,"href":503},"social.youtube","YouTube","https:\u002F\u002Fyoutube.com\u002F@getpieces",{"key":505,"label":506,"href":507},"github.org","GitHub organization","https:\u002F\u002Fgithub.com\u002Fpieces-app",{"key":509,"label":510,"href":511},"github.support","GitHub support","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport",{"key":513,"label":514,"href":515},"github.issues","GitHub issues","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fissues",{"key":517,"label":518,"href":519},"github.discussions","GitHub discussions","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fdiscussions",{"key":521,"label":522,"href":523},"github.documentation","GitHub documentation","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fdocumentation",{"key":525,"label":526,"href":527},"github.opensource","GitHub open source","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fopensource",{"key":529,"label":530,"href":531},"github.sdks.python","Python SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-python",{"key":533,"label":534,"href":535},"github.sdks.typescript","TypeScript SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-typescript",{"key":537,"label":538,"href":539},"github.sdks.dart","Dart SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-dart",{"key":541,"label":542,"href":543},"github.sdks.kotlin","Kotlin SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-kotlin",{"key":545,"label":546,"href":547},"github.plugins.obsidian","Obsidian plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fobsidian-pieces",{"key":549,"label":550,"href":247},"github.plugins.jupyterlab","JupyterLab plugin repository",{"key":552,"label":553,"href":554},"github.plugins.sublime","Sublime plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_sublime",{"key":556,"label":557,"href":558},"github.plugins.neovim","Neovim plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neovim",{"key":560,"label":561,"href":562},"github.cliAgent","CLI agent repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fcli-agent",{"key":564,"label":565,"href":566},"github.mcpDart","MCP Dart repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fmcp_dart",{"key":568,"label":569,"href":570},"github.awesomePieces","Awesome Pieces repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fawesome-pieces",{"key":572,"label":573,"href":66},"legal.privacyPolicy","Privacy policy",{"key":575,"label":576,"href":69},"legal.refundPolicy","Refund policy",{"key":578,"label":579,"href":72},"legal.terms","Terms",{"key":581,"label":582,"href":583},"legal.security","Legal security","https:\u002F\u002Fpieces.app\u002Flegal\u002Fsecurity",{"key":585,"label":586,"href":503},"videos.youtubeChannel","YouTube channel",{"key":588,"label":589,"href":590},"videos.gettingStartedDesktop","Getting started desktop video","https:\u002F\u002Fyoutu.be\u002FdUr1lRM_TYk",{"key":592,"label":593,"href":594},"videos.snippetDiscoveryxR","Snippet discovery video","https:\u002F\u002Fyoutu.be\u002FG6vb1USw-30",{"key":596,"label":597,"href":598},"sales.bookACall","Book a sales call","https:\u002F\u002Fcalendar.app.google\u002FWVUDtUfNy5Vst3sH7",{"key":600,"label":601,"href":602},"sales.enterprise","Enterprise form","https:\u002F\u002Fgetpieces.typeform.com\u002Fto\u002FaVQFTvpE",{"key":604,"label":605,"href":519},"sales.feedback","Feedback discussions",{"key":607,"label":608,"href":609},"sales.earlyAccess","Early access form","https:\u002F\u002Fgetpieces.typeform.com\u002Fearlyaccess",{"key":611,"label":612,"href":613},"sales.supportEmail","Support email","mailto:support@pieces.app",{"key":615,"label":616,"href":19},"routes.home","Home route",{"key":618,"label":619,"href":56},"routes.about","About route",{"key":621,"label":622,"href":12},"routes.downloads","Downloads route",{"key":624,"label":625,"href":626},"routes.thanks","Post-install thanks route","\u002Finstall\u002Fthanks",{"key":628,"label":629,"href":630},"routes.pricing","Pricing route","\u002Fpricing",{"key":632,"label":633,"href":634},"cta.freeTrial","Start free trial CTA (Pieces Pro) — used by \u002Fcampaigns\u002F* landing pages","https:\u002F\u002Fcampaigns.pieces.app\u002F",{"key":636,"label":637,"href":22},"routes.enterprise","Enterprise route",{"key":639,"label":640,"href":62},"routes.contact","Contact route",{"key":642,"label":643,"href":59},"routes.updates","Updates route",{"key":645,"label":646,"href":647},"routes.migrationError","Migration error route","\u002Fmigration\u002Ferror",{"key":649,"label":650,"href":651},"routes.authenticated","Post-login authenticated route","\u002Fauth\u002Fsigned-in",{"key":653,"label":654,"href":655},"routes.signed-out","Signed out route","\u002Fauth\u002Fsigned-out",{"key":657,"label":658,"href":659},"routes.authentication-error","Authentication error route","\u002Fauth\u002Ferror",{},"data\u002Fshared\u002Furls","NLkVE1lIUws518K-1QsSB6T-GvlXYIzcRL1HDhqarGQ",{"id":664,"title":665,"author":666,"authorPhoto":667,"authorPhotoAlt":668,"authorSlug":669,"body":670,"buttonText":1434,"buttonUrl":1435,"category":1436,"date":1437,"description":1438,"draft":1439,"editorsPick":32,"extension":1440,"featured":1439,"image":1441,"imageAlt":668,"meta":1442,"navigation":32,"ogImage":1443,"ogImageAlt":668,"path":1444,"seo":1445,"stem":1446,"tags":668,"__hash__":1447},"blog\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui.md","Building a daily productivity app with Pieces - Part 3: bringing it all together with Flutter UI","Bishoy Hany","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui\u002Fauthor.jpeg",null,"bishoy-hany",{"type":671,"value":672,"toc":1406},"minimark",[673,696,701,704,726,730,733,748,751,769,773,780,785,790,793,797,804,814,819,825,832,836,839,845,848,852,855,869,872,878,885,889,892,898,905,916,919,923,926,932,935,939,946,949,955,958,962,965,971,976,979,985,988,992,995,1001,1004,1008,1011,1017,1021,1024,1030,1034,1040,1046,1050,1053,1059,1063,1066,1072,1075,1079,1082,1088,1091,1095,1102,1109,1112,1117,1123,1128,1138,1144,1147,1150,1154,1157,1163,1166,1171,1177,1182,1199,1204,1221,1225,1239,1250,1266,1272,1281,1289,1293,1296,1316,1320,1323,1355,1358,1395,1398],[674,675,676,677,684,685,690,691,695],"p",{},"Welcome to Part 3! In ",[678,679,683],"a",{"href":680,"rel":681},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fbuilding-daily-standup-generator-with-pieces-api-sdk",[682],"nofollow","Part 1",", we built real-time sync with Pieces OS. In ",[678,686,689],{"href":687,"rel":688},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fbuilding-pieces-productivity-app-with-gemini-ai",[682],"Part 2",", we added Gemini AI to extract insights. Now it's time to make it ",[692,693,694],"strong",{},"beautiful"," with a Flutter UI! 🎨",[697,698,700],"h2",{"id":699},"what-were-building","What we're building",[674,702,703],{},"A clean Flutter app that shows:",[705,706,707,711,714,717,720,723],"ul",{},[708,709,710],"li",{},"📅 Daily recap cards",[708,712,713],{},"💼 Projects with status indicators",[708,715,716],{},"👥 People you collaborated with (with avatars!)",[708,718,719],{},"⏰ Reminders",[708,721,722],{},"📝 Notes and learnings",[708,724,725],{},"🔄 \"Load More\" pagination",[697,727,729],{"id":728},"the-challenge","The challenge",[674,731,732],{},"We have two services working perfectly:",[705,734,735,742],{},[708,736,737,741],{},[738,739,740],"code",{},"PiecesOSService"," - Real-time data sync",[708,743,744,747],{},[738,745,746],{},"DailyRecapService"," - AI-powered insights",[674,749,750],{},"Now we need to:",[752,753,754,757,760,763,766],"ol",{},[708,755,756],{},"Initialize both services when the app starts",[708,758,759],{},"Load recaps for recent days",[708,761,762],{},"Display them in beautiful cards",[708,764,765],{},"Handle loading states",[708,767,768],{},"Allow pagination (\"Load More\")",[697,770,772],{"id":771},"setting-up","Setting up",[674,774,775,776,779],{},"First, add the avatar package to ",[738,777,778],{},"pubspec.yaml",":",[674,781,782],{},[738,783,784],{},"dependencies:",[674,786,787],{},[738,788,789],{},"flutter_initicon: ^2.0.0  # For generating avatars from names",[674,791,792],{},"This package creates beautiful avatars from initials - perfect for showing collaborators!",[697,794,796],{"id":795},"integrating-the-services","Integrating the services",[674,798,799,800,803],{},"Start by removing anything that may be in your ",[738,801,802],{},"main.dart"," file and replace it with:",[805,806,811],"pre",{"className":807,"code":809,"language":810},[808],"language-text","import 'package:flutter\u002Fmaterial.dart';\nimport 'package:intl\u002Fintl.dart';\nimport 'package:timeago\u002Ftimeago.dart' as timeago;\nimport 'package:flutter_initicon\u002Fflutter_initicon.dart';\nimport 'services\u002Fpieces_os_service.dart';\nimport 'services\u002Fdaily_recap_service.dart';\nimport 'models\u002Fdaily_recap_models.dart';\n\nenum LoadingState {\n  loading,\n  healthy,\n  piecesOsNotRunning,\n  geminiApiKeyMissing,\n  somethingWentWrong,\n}\n\nvoid main() {\n  runApp(const MyApp());\n}\n\nclass MyApp extends StatelessWidget {\n  const MyApp({super.key});\n\n  @override\n  Widget build(BuildContext context) {\n    return MaterialApp(\n      title: 'Daily Recap Dashboard',\n      theme: ThemeData(\n        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),\n        useMaterial3: true,\n      ),\n      home: const MyHomePage(title: 'Daily Recap Dashboard'),\n    );\n  }\n}\n","text",[738,812,809],{"__ignoreMap":813},"",[674,815,816,817,779],{},"This sets up a Flutter app that displays a Daily Recap Dashboard with Material Design theming. Then we’re going to add two more classes to ",[738,818,802],{},[805,820,823],{"className":821,"code":822,"language":810},[808],"\u002F\u002F Place MyHomePage class after MyApp class \n\nclass MyHomePage extends StatefulWidget {\n  const MyHomePage({super.key, required this.title});\n\n  final String title;\n\n  @override\n  State\u003CMyHomePage> createState() => _MyHomePageState();\n}\n\nclass _MyHomePageState extends State\u003CMyHomePage> {\n  final PiecesOSService _piecesService = PiecesOSService();\n  DailyRecapService? _recapService;\n  \n  List\u003CDateTime> _loadedDays = [];\n  final Map\u003CDateTime, DailyRecapData> _recapsCache = {};\n  \n  LoadingState _loadingState = LoadingState.loading;\n  bool _isLoadingMore = false;\n  int _visibleCards = 3;  \u002F\u002F Show 3 cards initially\n  final Set\u003CDateTime> _regenerating = {};\n  final Map\u003CDateTime, String> _errorCache = {};\n  \n  @override\n  void initState() {\n    super.initState();\n    _initializeServices();\n  }\n}\n",[738,824,822],{"__ignoreMap":813},[674,826,827,828,831],{},"We use a ",[738,829,830],{},"LoadingState"," enum to track different states - loading, healthy (success), or various error conditions. This gives us better control over the UI.",[697,833,835],{"id":834},"the-initialization-dance","The initialization dance",[674,837,838],{},"This is where it all comes together:",[805,840,843],{"className":841,"code":842,"language":810},[808]," \u002F\u002F Add this method after initState() inside _MyHomePageState class\n\n  Future\u003Cvoid> _initializeServices() async {\n    setState(() => _loadingState = LoadingState.loading);\n\n    try {\n      try {\n        \u002F\u002F Step 1: Initialize Pieces OS\n        await _piecesService.initialize();\n      } catch (e) {\n        setState(() {\n          _loadingState = LoadingState.piecesOsNotRunning;\n        });\n        return;\n      }\n\n      \u002F\u002F Step 2: Wait for summaries to load (WebSocket takes time!)\n      await _piecesService.waitForInitialSync();\n\n      \u002F\u002F Step 3: Check for Gemini API key\n      const apiKey = String.fromEnvironment('GEMINI_API_KEY');\n      if (apiKey == '') {\n        setState(() {\n          _loadingState = LoadingState.geminiApiKeyMissing;\n        });\n        return;\n      }\n\n      \u002F\u002F Step 4: Initialize Gemini service\n      if (apiKey.isNotEmpty) {\n        _recapService = DailyRecapService(apiKey: apiKey);\n      }\n\n      \u002F\u002F Step 5: Get days with data\n      _loadedDays = _piecesService.getDaysWithSummaries();\n\n      \u002F\u002F Step 6: Load recaps for first 3 days\n      await _loadRecapsForVisibleDays();\n\n      setState(() => _loadingState = LoadingState.healthy);\n    } catch (e) {\n      print('Error initializing: $e');\n      setState(() => _loadingState = LoadingState.somethingWentWrong);\n    }\n  }\n",[738,844,842],{"__ignoreMap":813},[674,846,847],{},"This initializes the Pieces OS service, waits for summaries to sync, checks for a Gemini API key, creates the recap service, and loads the first 3 days of recaps, updating the loading state accordingly.",[697,849,851],{"id":850},"loading-recaps","Loading recaps",[674,853,854],{},"For each visible day, we:",[752,856,857,860,863,866],{},[708,858,859],{},"Fetch summaries with their content (from annotations)",[708,861,862],{},"Send the summaries to Gemini for analysis",[708,864,865],{},"Store the summaries in memory (for this session)",[708,867,868],{},"Update the UI",[674,870,871],{},"We've connected to Pieces OS, waited for summaries to sync, checked for a Gemini API key, and collected all days that have summaries. We know which days have data, but we haven't generated any recaps yet.",[805,873,876],{"className":874,"code":875,"language":810},[808]," \u002F\u002F Add this method after _initializeServices() inside _MyHomePageState class\n  \u002F\u002F (the one you just made)\n\n  Future\u003Cvoid> _loadRecapsForVisibleDays() async {\n    if (_recapService == null) return;\n\n    final daysToLoad = _loadedDays.take(_visibleCards).toList();\n\n    for (final day in daysToLoad) {\n      if (!_recapsCache.containsKey(day)) {\n        try {\n          \u002F\u002F Fetch summaries with content\n          final summariesWithContent = await getSummariesWithContentForDay(\n              _piecesService, day);\n\n          if (summariesWithContent.isNotEmpty) {\n            \u002F\u002F Generate recap with Gemini\n            final recap = await _recapService!.generateDailyRecap(\n              date: day,\n              summaries: summariesWithContent,\n            );\n\n            setState(() {\n              _recapsCache[day] = recap;\n            });\n          }\n        } catch (e) {\n          print('Error loading recap for $day: $e');\n        }\n      }\n    }\n  }\n",[738,877,875],{"__ignoreMap":813},[674,879,880,881,884],{},"Notice we store recaps in memory (",[738,882,883],{},"_recapsCache",") - so we only call Gemini once per day during this session. When you restart the app, it will regenerate everything. (We'll fix that with persistent caching in Part 4!)",[697,886,888],{"id":887},"the-wrap-layout-responsive-cards","The wrap layout — responsive cards",[674,890,891],{},"Here's where the UI shines! Cards flow horizontally and wrap to the next row when they don't fit:",[805,893,896],{"className":894,"code":895,"language":810},[808],"\u002F\u002F Add this method after _loadRecapsForVisibleDays() inside _MyHomePageState \n\nclass\n  Widget _buildRecapsList() {\n    final visibleDays = _loadedDays.take(_visibleCards).toList();\n\n    return SingleChildScrollView(\n      padding: const EdgeInsets.all(16),\n      child: Column(\n        children: [\n          \u002F\u002F Cards flow horizontally, wrap to next row when overflow\n          Wrap(\n            spacing: 16,  \u002F\u002F Horizontal spacing between cards\n            runSpacing: 16,  \u002F\u002F Vertical spacing between rows\n            children: visibleDays.map((day) {\n              final recap = _recapsCache[day];\n              \n              return SizedBox(\n                width: 400,  \u002F\u002F Fixed width for each card\n                child: recap != null\n                    ? DailyRecapCard(recap: recap)\n                    : _buildLoadingCard(day),\n              );\n            }).toList(),\n          ),\n          \n          \u002F\u002F Load More button centered\n          if (visibleDays.length \u003C _loadedDays.length)\n            Center(\n              child: ElevatedButton.icon(\n                onPressed: _loadMore,\n                icon: const Icon(Icons.expand_more),\n                label: const Text('Load More Days'),\n              ),\n            ),\n        ],\n      ),\n    );\n  }\n",[738,897,895],{"__ignoreMap":813},[674,899,900,901,904],{},"The ",[738,902,903],{},"Wrap"," widget is perfect for this! It:",[705,906,907,910,913],{},[708,908,909],{},"Places cards horizontally first (left to right)",[708,911,912],{},"When a card doesn't fit, wraps to the next row",[708,914,915],{},"Handles different screen sizes automatically",[674,917,918],{},"On a typical desktop, you might see 3 cards in the first row, then more rows below as you load more data. On a laptop, maybe 2 per row. It just works!",[697,920,922],{"id":921},"the-load-more-feature","The \"Load more\" feature",[674,924,925],{},"Let’s build the iconic “load more” button in our UI:",[805,927,930],{"className":928,"code":929,"language":810},[808]," \u002F\u002F Add this method after _buildRecapsList() inside _MyHomePageState class\n\n  Future\u003Cvoid> _loadMore() async {\n    setState(() => _isLoadingMore = true);\n    \n    \u002F\u002F Increase visible count\n    setState(() => _visibleCards += 3);\n    \n    \u002F\u002F Load the new days\n    await _loadRecapsForVisibleDays();\n    \n    setState(() => _isLoadingMore = false);\n  }\n",[738,931,929],{"__ignoreMap":813},[674,933,934],{},"Only show the button if there are more days to load! When clicked, it adds 3 more cards, which flow into the wrap layout naturally.",[697,936,938],{"id":937},"building-the-daily-recap-card","Building the Daily Recap Card",[674,940,941,942,945],{},"Here's where the magic happens. We take the ",[738,943,944],{},"DailyRecapData"," from our service and transform it into a beautiful card:",[674,947,948],{},"Let’s add a formatter function to format the date",[805,950,953],{"className":951,"code":952,"language":810},[808],"\u002F\u002F Place this at the top under the MyApp class\nString dateFormatter(DateTime date) {\n  final now = DateTime.now();\n  final difference = now.difference(date);\n\n  if (difference.inDays == 0) {\n    return 'Today';\n  } else if (difference.inDays == 1) {\n    return 'Yesterday';\n  } else {\n    return timeago.format(date);\n  }\n}\n\n\u002F\u002F Add this widget class after _MyHomePageState class closes\n\nclass DailyRecapCard extends StatelessWidget {\n  final DailyRecapData recap;\n\n  const DailyRecapCard({super.key, required this.recap});\n\n  @override\n  Widget build(BuildContext context) {\n    return Card(\n      elevation: 4,\n      child: Padding(\n        padding: const EdgeInsets.all(16.0),\n        child: Column(\n          crossAxisAlignment: CrossAxisAlignment.start,\n          children: [\n            \u002F\u002F Date Header\n            Text(\n              dateFormatter(recap.date),\n              style: Theme.of(context).textTheme.headlineSmall,\n            ),\n            Text(\n              DateFormat('MMMM d, y').format(recap.date),\n              style: Theme.of(context).textTheme.bodyMedium\n                  ?.copyWith(color: Colors.grey.shade600),\n            ),\n            const SizedBox(height: 16),\n\n            \u002F\u002F Daily Summary with icon\n            Card(\n              color: Colors.blue.shade50,\n              child: Padding(\n                padding: const EdgeInsets.all(16.0),\n                child: Row(\n                  children: [\n                    Icon(Icons.summarize, color: Colors.blue.shade700),\n                    const SizedBox(width: 12),\n                    Expanded(child: Text(recap.summary)),\n                  ],\n                ),\n              ),\n            ),\n            \n            \u002F\u002F ... rest of the sections\n          ],\n        ),\n      ),\n    );\n  }\n}\n",[738,954,952],{"__ignoreMap":813},[674,956,957],{},"The card we just added is responsive and shows different sections based on what data is available.",[697,959,961],{"id":960},"clean-bullet-point-style","Clean bullet-point style",[674,963,964],{},"For readability, I kept it simple - no nested cards, just clean bullet points:",[805,966,969],{"className":967,"code":968,"language":810},[808],"\\\\ class DailyRecapCard extends StatelessWidget {\n \\\\ final DailyRecapData recap;\n\n \\\\ const DailyRecapCard({super.key, required this.recap});\n\n  \u002F\u002F Add this helper method inside DailyRecapCard class\n\n  List\u003CWidget> _buildProjectWidgets(List\u003CProjectData> projects) {\n    return projects.map((project) {\n      return Padding(\n        padding: const EdgeInsets.only(bottom: 8),\n        child: Row(\n          children: [\n            Icon(statusIcon, color: statusColor, size: 20),\n            const SizedBox(width: 8),\n            Expanded(\n              child: Column(\n                crossAxisAlignment: CrossAxisAlignment.start,\n                children: [\n                  Row(\n                    children: [\n                      Text(project.name, \n                        style: TextStyle(fontWeight: FontWeight.bold)),\n                      Spacer(),\n                      \u002F\u002F Status badge\n                      Container(...status badge...),\n                    ],\n                  ),\n                  Text(project.description, \n                    style: TextStyle(color: Colors.grey)),\n                ],\n              ),\n            ),\n          ],\n        ),\n      );\n    }).toList();\n  }\n\n  @override\n  Widget build(BuildContext context) {\n    \u002F\u002F ... existing build method ...\n  }\n}\n",[738,970,968],{"__ignoreMap":813},[972,973,975],"h3",{"id":974},"reminders-notes","Reminders & notes",[674,977,978],{},"Next, let’s add a reminders and notes widget!",[805,980,983],{"className":981,"code":982,"language":810},[808]," \u002F\u002F ... rest of the sections\n            \n       \u002F\u002F Reminders section\n\n       if (recap.reminders.isNotEmpty) ...[\n         const SizedBox(height: 16),\n         Text(\"Reminders\", style: Theme.of(context).textTheme.titleMedium),\n         const SizedBox(height: 8),\n         ...recap.reminders.map((reminder) {\n           return Card(\n             margin: const EdgeInsets.only(bottom: 8),\n             color: Colors.amber.shade50,\n             child: ListTile(\n               leading: Icon(Icons.alarm, color: Colors.amber.shade700),\n               title: Text(reminder),\n             ),\n           );\n         }),\n       ],\n",[738,984,982],{"__ignoreMap":813},[674,986,987],{},"No nested cards, no complexity. Just clean, readable bullet points with icons!",[697,989,991],{"id":990},"people-avatars-with-initicon","People avatars with initicon",[674,993,994],{},"This is one of my favorite touches - generating avatars from names:",[805,996,999],{"className":997,"code":998,"language":810},[808],"\u002F\u002F Add this helper method after _buildProjectWidgets() inside DailyRecapCard class\n\n  List\u003CWidget> _buildPeopleWidgets(List\u003CString> people) {\n    return people.map((person) {\n      return Tooltip(\n        message: person,\n        child: Initicon(\n          text: person,\n          size: 40,\n          backgroundColor: Colors.primaries[\n            person.hashCode % Colors.primaries.length\n          ],\n        ),\n      );\n    }).toList();\n  }\n",[738,1000,998],{"__ignoreMap":813},[674,1002,1003],{},"Each person gets a unique color based on their name's hash. Looks great and no need for actual images!",[697,1005,1007],{"id":1006},"project-status-indicators","Project status indicators",[674,1009,1010],{},"Projects show their status with icons and colored badges:",[805,1012,1015],{"className":1013,"code":1014,"language":810},[808]," \u002F\u002F Add this helper method before _buildProjectWidgets() inside DailyRecapCard class\n\n  Widget _buildProjectWidget(ProjectData project) {\n    Color statusColor;\n    String statusText;\n    IconData statusIcon;\n    switch (project.status) {\n      case ProjectStatus.completed:\n        statusColor = Colors.green;\n        statusText = \"Completed\";\n        statusIcon = Icons.check_circle;\n        break;\n      case ProjectStatus.inProgress:\n        statusColor = Colors.orange;\n        statusText = \"In Progress\";\n        statusIcon = Icons.sync;\n        break;\n      case ProjectStatus.notStarted:\n        statusColor = Colors.red;\n        statusText = \"Not Started\";\n        statusIcon = Icons.radio_button_unchecked;\n        break;\n    }\n\n    return Card(\n      child: ListTile(\n        leading: Icon(statusIcon, color: statusColor),\n        title: Text(project.name, \n          style: const TextStyle(fontWeight: FontWeight.bold)),\n        subtitle: Text(project.description),\n        trailing: Container(\n          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),\n          decoration: BoxDecoration(\n            color: statusColor,\n            borderRadius: BorderRadius.circular(12),\n            border: Border.all(color: statusColor),\n          ),\n          child: Text(\n            statusText,\n            style: TextStyle(\n              color: Colors.white,\n              fontWeight: FontWeight.bold,\n            ),\n          ),\n        ),\n      ),\n    );\n  }\n",[738,1016,1014],{"__ignoreMap":813},[697,1018,1020],{"id":1019},"notes-sections-the-new-addition","Notes sections — the new addition",[674,1022,1023],{},"Let’s add notes and reminders section to show important learnings from the day:",[805,1025,1028],{"className":1026,"code":1027,"language":810},[808],"\u002F\u002F Notes section - add after reminders section\n\n       if (recap.notes.isNotEmpty) ...[\n         const SizedBox(height: 16),\n         Text(\"Notes\", style: Theme.of(context).textTheme.titleMedium),\n         const SizedBox(height: 8),\n           ...recap.notes.map((note) {\n           return Card(\n             margin: const EdgeInsets.only(bottom: 8),\n             color: Colors.purple.shade50,\n             child: ListTile(\n               leading: Icon(Icons.lightbulb, color: Colors.purple.shade700),\n               title: Text(note),\n             ),\n           );\n        }),\n      ],\n",[738,1029,1027],{"__ignoreMap":813},[697,1031,1033],{"id":1032},"handling-loading-states","Handling loading states",[674,1035,1036,1037,1039],{},"Good UX means showing what's happening. We use the ",[738,1038,830],{}," enum to render different screens:",[805,1041,1044],{"className":1042,"code":1043,"language":810},[808],"\u002F\u002F Add this build() method after _loadMore() inside _MyHomePageState class\n\n  @override\n  Widget build(BuildContext context) {\n    return Scaffold(\n      appBar: AppBar(title: Text(widget.title)),\n      body: _loadingState == LoadingState.loading\n          ? _buildLoadingScreen()\n          : _loadingState == LoadingState.healthy\n          ? _buildRecapsList()\n          : _buildErrorScreen(),\n    );\n  }\n",[738,1045,1043],{"__ignoreMap":813},[972,1047,1049],{"id":1048},"the-loading-screen","The loading screen",[674,1051,1052],{},"Now, let’s build a loading screen!",[805,1054,1057],{"className":1055,"code":1056,"language":810},[808],"  \u002F\u002F Add this method after _buildRecapsList() inside _MyHomePageState class\n\n  Widget _buildLoadingScreen() {\n    return const Center(\n      child: Column(\n        mainAxisAlignment: MainAxisAlignment.center,\n        children: [\n          CircularProgressIndicator(),\n          SizedBox(height: 20),\n          Text('Connecting to Pieces OS...'),\n          SizedBox(height: 10),\n          Text('This may take a few seconds'),\n        ],\n      ),\n    );\n  }\n",[738,1058,1056],{"__ignoreMap":813},[972,1060,1062],{"id":1061},"the-error-screen","The error screen",[674,1064,1065],{},"Inevitably, we’ll hit some errors. Let’s build a screen for that!",[805,1067,1070],{"className":1068,"code":1069,"language":810},[808],"  \u002F\u002F Add this method after _buildLoadingScreen() inside _MyHomePageState class\n\n Widget _buildErrorScreen() {\n    String message;\n    IconData icon;\n    \n    switch (_loadingState) {\n      case LoadingState.piecesOsNotRunning:\n        message = 'Pieces OS is not running. Please start Pieces OS and try again.';\n        icon = Icons.cloud_off;\n        break;\n      case LoadingState.geminiApiKeyMissing:\n        message = 'Gemini API key is missing. Please set the API key and restart the app.';\n        icon = Icons.vpn_key_off;\n        break;\n      case LoadingState.somethingWentWrong:\n      default:\n        message = 'Something went wrong. Please try again later.';\n        icon = Icons.error_outline;\n        break;\n    }\n    \n    return Center(\n      child: Column(\n        mainAxisAlignment: MainAxisAlignment.center,\n        children: [\n          Icon(icon, size: 64, color: Colors.red),\n          const SizedBox(height: 20),\n          Text(\n            message,\n            style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),\n            textAlign: TextAlign.center,\n          ),\n        ],\n      ),\n    );\n  }\n",[738,1071,1069],{"__ignoreMap":813},[674,1073,1074],{},"Now users get specific, actionable error messages instead of generic failures!",[972,1076,1078],{"id":1077},"loading-individual-cards","Loading individual cards",[674,1080,1081],{},"Next, let’s get the cards loading in individually:",[805,1083,1086],{"className":1084,"code":1085,"language":810},[808],"  \u002F\u002F Add this method after _buildRecapsList() inside _MyHomePageState class\n\n  Widget _buildLoadingCard(DateTime date) {\n    return Card(\n      elevation: 4,\n      child: Padding(\n        padding: const EdgeInsets.all(16.0),\n        child: Column(\n          children: [\n            Text(DateFormat('EEEE, MMMM d').format(date)),\n            const SizedBox(height: 10),\n            const CircularProgressIndicator(),\n            const SizedBox(height: 8),\n            const Text('Generating recap with AI...'),\n          ],\n        ),\n      ),\n    );\n  }\n",[738,1087,1085],{"__ignoreMap":813},[674,1089,1090],{},"This keeps the users in the loop, so they know exactly what’s going on!",[697,1092,1094],{"id":1093},"the-macos-network-permission-issue","The macOS network permission issue",[674,1096,1097,1098,1101],{},"MacOS apps are \"sandboxed\" by default - think of it like a security fence that blocks your app from accessing the internet or other apps on your computer. This includes connecting to ",[738,1099,1100],{},"localhost"," (your own computer).",[674,1103,1104,1105,1108],{},"When you try to connect to Pieces OS at ",[738,1106,1107],{},"localhost:39300",", macOS blocks it because your app doesn't have permission to make network connections.",[674,1110,1111],{},"You can fix this by:",[752,1113,1114],{},[708,1115,1116],{},"Find these two files in your project:",[674,1118,1119,1120],{},"   - ",[738,1121,1122],{},"macos\u002FRunner\u002FDebugProfile.entitlements",[674,1124,1119,1125],{},[738,1126,1127],{},"macos\u002FRunner\u002FRelease.entitlements",[752,1129,1131],{"start":1130},2,[708,1132,1133,1134,1137],{},"Open each file and add these two lines inside the ",[738,1135,1136],{},"\u003Cdict>"," section:",[805,1139,1142],{"className":1140,"code":1141,"language":810},[808]," \u003Ckey>com.apple.security.network.client\u003C\u002Fkey>\n \u003Ctrue\u002F>\n",[738,1143,1141],{"__ignoreMap":813},[674,1145,1146],{},"   3. Save both files.",[674,1148,1149],{},"That's it! Your app can now connect to Pieces OS and other network services.",[697,1151,1153],{"id":1152},"running-our-application","Running our application",[674,1155,1156],{},"Which sets up your workspace for your device, then you can run:",[805,1158,1161],{"className":1159,"code":1160,"language":810},[808],"flutter run -d macos --dart-define=GEMINI_API_KEY=your-key-here\n",[738,1162,1160],{"__ignoreMap":813},[674,1164,1165],{},"And boom! You get:",[674,1167,1168],{},[692,1169,1170],{},"On Startup:",[805,1172,1175],{"className":1173,"code":1174,"language":810},[808],"Connecting to Pieces OS...\nThis may take a few seconds\n",[738,1176,1174],{"__ignoreMap":813},[674,1178,1179],{},[692,1180,1181],{},"After Loading:",[705,1183,1184,1187,1190,1193,1196],{},[708,1185,1186],{},"3 beautiful daily recap cards",[708,1188,1189],{},"Cards flow horizontally, wrap to next row if needed",[708,1191,1192],{},"Each card 400px wide, responsive to screen size",[708,1194,1195],{},"Each showing summary, projects, people, reminders, notes",[708,1197,1198],{},"\"Load More Days\" button at the bottom",[674,1200,1201],{},[692,1202,1203],{},"When You Click \"Load More Days\":",[705,1205,1206,1209,1212,1215,1218],{},[708,1207,1208],{},"Shows loading spinner",[708,1210,1211],{},"Fetches 3 more days",[708,1213,1214],{},"Generates their recaps with Gemini",[708,1216,1217],{},"Adds them to the wrap layout",[708,1219,1220],{},"Seamless expansion!",[697,1222,1224],{"id":1223},"missing-icons","Missing icons?",[674,1226,1227,1228,1231,1232,1231,1235,1238],{},"If you find that the icons (like ",[738,1229,1230],{},"Icons.summarize",", ",[738,1233,1234],{},"Icons.alarm",[738,1236,1237],{},"Icons.lightbulb",", etc.) are not displaying correctly in your Flutter application, it usually means the font that provides these icons (Material Icons) is not being bundled or referenced correctly, or a specific dependency is missing an implicit requirement.",[752,1240,1241],{},[708,1242,1243,1246,1247,1249],{},[692,1244,1245],{},"Open"," ",[738,1248,778],{},".",[674,1251,1252,1246,1255,1246,1258,1261,1262,1265],{},[692,1253,1254],{},"Ensure the",[738,1256,1257],{},"uses-material-design: true",[692,1259,1260],{},"line is present and uncommented"," under the ",[738,1263,1264],{},"flutter:"," section. This line explicitly tells Flutter to include the Material Icons font in your application bundle.",[805,1267,1270],{"className":1268,"code":1269,"language":810},[808],"# Inside pubspec.yaml\nflutter:\n  uses-material-design: true\n",[738,1271,1269],{"__ignoreMap":813},[674,1273,1274,1246,1277,1280],{},[692,1275,1276],{},"Run",[738,1278,1279],{},"flutter pub get"," in your terminal after confirming or adding the line to fetch dependencies and update the project configuration.",[752,1282,1283],{},[708,1284,1285,1288],{},[692,1286,1287],{},"Restart the Application",": If the app was already running, you must stop and restart it (not just hot reload or hot restart) to load the new resource configuration.",[697,1290,1292],{"id":1291},"what-weve-built","What we've built",[674,1294,1295],{},"At this point, you have a fully functional productivity that:",[705,1297,1298,1301,1304,1307,1310,1313],{},[708,1299,1300],{},"✅ Syncs with Pieces OS in real-time",[708,1302,1303],{},"✅ Uses AI to generate daily insights",[708,1305,1306],{},"✅ Displays beautiful, responsive cards",[708,1308,1309],{},"✅ Handles loading and error states",[708,1311,1312],{},"✅ Supports pagination with \"Load More\"",[708,1314,1315],{},"✅ Shows projects, people, reminders, and notes",[697,1317,1319],{"id":1318},"but-theres-room-for-improvement","But there's room for improvement...",[674,1321,1322],{},"Right now, there are a few limitations:",[752,1324,1325,1331,1337,1343,1349],{},[708,1326,1327,1330],{},[692,1328,1329],{},"Every restart regenerates all recaps"," - Slow and wastes API calls",[708,1332,1333,1336],{},[692,1334,1335],{},"Can't refresh a single recap"," - Stuck with what you got",[708,1338,1339,1342],{},[692,1340,1341],{},"Error handling could be better"," - Just console logs",[708,1344,1345,1348],{},[692,1346,1347],{},"No tests"," - Hard to maintain with confidence",[708,1350,1351,1354],{},[692,1352,1353],{},"No CI\u002FCD"," - Manual testing every time",[674,1356,1357],{},"In Part 4, we'll tackle all of these! We'll add:",[705,1359,1360,1367,1374,1381,1388],{},[708,1361,1362,1363,1366],{},"💾 ",[692,1364,1365],{},"Persistent caching with Hive"," - Instant loads on restart",[708,1368,1369,1370,1373],{},"🔄 ",[692,1371,1372],{},"Regenerate button"," - Refresh any recap on demand",[708,1375,1376,1377,1380],{},"❌ ",[692,1378,1379],{},"Better error handling"," - Beautiful error cards with retry",[708,1382,1383,1384,1387],{},"✅ ",[692,1385,1386],{},"Automated tests"," - Catch bugs before they ship",[708,1389,1390,1391,1394],{},"🚀 ",[692,1392,1393],{},"CI\u002FCD pipeline"," - Automated testing and builds",[674,1396,1397],{},"See you in Part 4! 🚀",[674,1399,1400,1405],{},[678,1401,1404],{"href":1402,"rel":1403},"https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fblog-dart-daily-stand-up-generator",[682],"Reference GitHub"," to view the full project.",{"title":813,"searchDepth":1130,"depth":1130,"links":1407},[1408,1409,1410,1411,1412,1413,1414,1415,1416,1417,1421,1422,1423,1424,1429,1430,1431,1432,1433],{"id":699,"depth":1130,"text":700},{"id":728,"depth":1130,"text":729},{"id":771,"depth":1130,"text":772},{"id":795,"depth":1130,"text":796},{"id":834,"depth":1130,"text":835},{"id":850,"depth":1130,"text":851},{"id":887,"depth":1130,"text":888},{"id":921,"depth":1130,"text":922},{"id":937,"depth":1130,"text":938},{"id":960,"depth":1130,"text":961,"children":1418},[1419],{"id":974,"depth":1420,"text":975},3,{"id":990,"depth":1130,"text":991},{"id":1006,"depth":1130,"text":1007},{"id":1019,"depth":1130,"text":1020},{"id":1032,"depth":1130,"text":1033,"children":1425},[1426,1427,1428],{"id":1048,"depth":1420,"text":1049},{"id":1061,"depth":1420,"text":1062},{"id":1077,"depth":1420,"text":1078},{"id":1093,"depth":1130,"text":1094},{"id":1152,"depth":1130,"text":1153},{"id":1223,"depth":1130,"text":1224},{"id":1291,"depth":1130,"text":1292},{"id":1318,"depth":1130,"text":1319},"Get started","https:\u002F\u002Fpieces.app\u002F","Software Development","2025-12-04T00:00:00.000Z","Part 3: Flutter UI to bring your Pieces daily productivity app together.",false,"md","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui\u002Fhero.png",{},"https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fopen-graph\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui.png","\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui",{"title":665,"description":1438},"blog\u002Fbuilding-pieces-productivity-with-flutter-ui","HMxGOAd-Sa-usTyU7iGejPxbPP1sC22A_qv3OzZp6-o",{"id":1449,"title":666,"body":1450,"description":1454,"draft":1439,"extension":1440,"meta":1457,"navigation":32,"path":1458,"photo":1459,"photoAlt":668,"seo":1460,"stem":1461,"__hash__":1462},"authors\u002Fauthors\u002Fbishoy-hany.md",{"type":671,"value":1451,"toc":1455},[1452],[674,1453,1454],{},"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":813,"searchDepth":1130,"depth":1130,"links":1456},[],{},"\u002Fauthors\u002Fbishoy-hany","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fauthors\u002Fbishoy-hany.jpg",{"title":666,"description":1454},"authors\u002Fbishoy-hany","_m1q8mqNk1I4j1V0_YJSK0zuMS48DBT4Nqw9e3PZjMM",{"left":1464,"top":1464,"width":1465,"height":1465,"rotate":1464,"vFlip":1439,"hFlip":1439,"body":1466},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>"]