[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"navigation":3,"url-settings":80,"blog-\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui":589,"blog-author-\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui":1373},{"id":4,"extension":5,"footer":6,"header":66,"meta":77,"stem":78,"__hash__":79},"navigation\u002Fdata\u002Fshared\u002Fnavigation.yml","yml",{"brand":7,"columns":10,"legal":56},{"name":8,"tagline":9},"Pieces","The memory layer for modern work.",[11,26,41],{"title":12,"links":13},"Product",[14,17,21,24],{"label":15,"href":16},"Pieces Desktop","\u002Fdownloads",{"label":18,"href":19,"external":20},"Pieces MCP","url:docs.mcp.overview",true,{"label":22,"href":23,"external":20},"Pieces APIs","url:docs.api",{"label":25,"href":16},"Downloads",{"title":27,"links":28},"Resources",[29,32,35,38],{"label":30,"href":31,"external":20},"Documentation","url:docs.home",{"label":33,"href":34},"Blog","\u002Fblog",{"label":36,"href":37},"Changelog","\u002Fchangelog",{"label":39,"href":40,"external":20},"GitHub","url:github.org",{"title":42,"links":43},"Company",[44,47,50,53],{"label":45,"href":46},"About","\u002Fabout",{"label":48,"href":49},"Enterprise","\u002Fenterprise",{"label":51,"href":52,"external":20},"Discord","url:social.discord",{"label":54,"href":55,"external":20},"X \u002F Twitter","url:social.x",[57,60,63],{"label":58,"href":59,"external":20},"Privacy Policy","url:legal.privacyPolicy",{"label":61,"href":62,"external":20},"Refund Policy","url:legal.refundPolicy",{"label":64,"href":65,"external":20},"Terms of Service","url:legal.terms",{"links":67,"signIn":68,"contact":71,"cta":74},[],{"label":69,"href":70},"Sign in","url:portal.home",{"label":72,"href":73},"Contact sales","url:site.contact",{"label":75,"href":76},"Download","url:routes.downloads",{},"data\u002Fshared\u002Fnavigation","Ia8tCWWqcGvuaIro8jwZ3HH-MwI66yqJpWshASJdYQ0",{"id":81,"extension":5,"links":82,"meta":586,"stem":587,"__hash__":588},"urlSettings\u002Fdata\u002Fshared\u002Furls.yml",[83,87,91,95,99,103,107,111,115,119,123,127,131,135,139,143,147,151,155,159,163,167,171,175,179,183,187,191,195,199,203,207,211,215,219,223,227,231,235,238,242,246,249,253,257,261,265,269,273,277,281,285,289,293,297,301,305,309,313,317,321,325,329,333,337,341,345,349,353,357,361,365,369,373,377,381,385,389,393,396,400,404,408,412,416,420,423,426,429,432,436,440,444,448,452,456,460,464,468,472,476,480,484,488,492,495,499,503,507,511,515,519,523,527,531,534,538,542,546,550,553,557,561,565,568,571,575,579,582],{"key":84,"label":85,"href":86},"downloads.desktop","Desktop download page","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdownload",{"key":88,"label":89,"href":90},"downloads.macOS.dmgArm64","macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg-arm64\u002Fdownload",{"key":92,"label":93,"href":94},"downloads.macOS.dmgIntel","macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fdmg\u002Fdownload",{"key":96,"label":97,"href":98},"downloads.macOS.pkg","macOS PKG","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fmacos_packaging\u002Fpkg\u002Fdownload",{"key":100,"label":101,"href":102},"downloads.windows.appinstaller","Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fpieces_for_x.appinstaller",{"key":104,"label":105,"href":106},"downloads.windows.exe","Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_for_x\u002Fwindows-exe\u002Fdownload",{"key":108,"label":109,"href":110},"downloads.windows.suiteManager","Windows Suite Manager","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fpieces_suite_windows\u002Fappinstaller\u002Fdownload",{"key":112,"label":113,"href":114},"downloads.linux.flatpakRepo","Linux Flatpak repository","https:\u002F\u002Fbuilds.pieces.app\u002Fpieces-flatpak-repo\u002Fpieces-flatpak.flatpakrepo",{"key":116,"label":117,"href":118},"downloads.linux.snapDesktop","Linux Snap Desktop","https:\u002F\u002Fsnapcraft.io\u002Fpieces-for-developers",{"key":120,"label":121,"href":122},"downloads.linux.snapPiecesOS","Linux Snap PiecesOS","https:\u002F\u002Fsnapcraft.io\u002Fpieces-os",{"key":124,"label":125,"href":126},"downloads.piecesOS.macOS.dmgArm64","PiecesOS macOS DMG Apple Silicon","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg-arm64\u002Fdownload",{"key":128,"label":129,"href":130},"downloads.piecesOS.macOS.dmgIntel","PiecesOS macOS DMG Intel","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fdmg\u002Fdownload",{"key":132,"label":133,"href":134},"downloads.piecesOS.windows.appinstaller","PiecesOS Windows App Installer","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fappinstaller\u002Fos_server.appinstaller",{"key":136,"label":137,"href":138},"downloads.piecesOS.windows.exe","PiecesOS Windows EXE","https:\u002F\u002Fbuilds.pieces.app\u002Fstages\u002Fproduction\u002Fos_server\u002Fwindows-exe\u002Fdownload",{"key":140,"label":141,"href":142},"downloads.guides.macOS","macOS installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fmacos-installation-guide",{"key":144,"label":145,"href":146},"downloads.guides.windows","Windows installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Fwindows-installation-guide",{"key":148,"label":149,"href":150},"downloads.guides.linux","Linux installation guide","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Flinux-installation-guide",{"key":152,"label":153,"href":154},"downloads.guides.piecesOS","PiecesOS manual installation","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fmanual-installation",{"key":156,"label":157,"href":158},"extensions.chrome","Chrome extension","https:\u002F\u002Fchrome.google.com\u002Fwebstore\u002Fdetail\u002Fpieces-save-code-snippets\u002Figbgibhbfonhmjlechmeefimncpekepm",{"key":160,"label":161,"href":162},"extensions.firefox","Firefox add-on","https:\u002F\u002Faddons.mozilla.org\u002Fen-US\u002Ffirefox\u002Faddon\u002Fpieces-save-code-from-the-web\u002F",{"key":164,"label":165,"href":166},"extensions.edge","Edge add-on","https:\u002F\u002Fmicrosoftedge.microsoft.com\u002Faddons\u002Fdetail\u002Fpieces-save-code-snippet\u002Fhglfimcdgonaeeobjckfdabcldfidmim",{"key":168,"label":169,"href":170},"extensions.vscode","VS Code extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.pieces-vscode",{"key":172,"label":173,"href":174},"extensions.visualStudio","Visual Studio extension","https:\u002F\u002Fmarketplace.visualstudio.com\u002Fitems?itemName=MeshIntelligentTechnologiesInc.PiecesVisualStudio",{"key":176,"label":177,"href":178},"extensions.jetbrains","JetBrains plugin","https:\u002F\u002Fplugins.jetbrains.com\u002Fplugin\u002F17328-pieces--save-search-share--reuse-code-snippets",{"key":180,"label":181,"href":182},"extensions.obsidian","Obsidian plugin","https:\u002F\u002Fobsidian.md\u002Fplugins?id=pieces-for-developers",{"key":184,"label":185,"href":186},"extensions.sublime","Sublime package","https:\u002F\u002Fpackagecontrol.io\u002Fpackages\u002FPieces",{"key":188,"label":189,"href":190},"extensions.neovim","Neovim plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neo_vim",{"key":192,"label":193,"href":194},"extensions.jupyterlab","JupyterLab plugin","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fjupyterlab-pieces",{"key":196,"label":197,"href":198},"extensions.cli","Pieces CLI","https:\u002F\u002Fpypi.org\u002Fproject\u002Fpieces-cli\u002F",{"key":200,"label":201,"href":202},"docs.home","Documentation home","https:\u002F\u002Fdocs.pieces.app",{"key":204,"label":205,"href":206},"docs.getStarted","Get started docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces",{"key":208,"label":209,"href":210},"docs.api","API docs","https:\u002F\u002Fdocs.pieces.app\u002Fapi",{"key":212,"label":213,"href":214},"docs.desktop.overview","Desktop overview","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop",{"key":216,"label":217,"href":218},"docs.desktop.onboarding","Desktop onboarding","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fonboarding",{"key":220,"label":221,"href":222},"docs.desktop.timeline","Desktop timeline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Ftimeline",{"key":224,"label":225,"href":226},"docs.desktop.summaries","Desktop summaries docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fsingle-click-summaries",{"key":228,"label":229,"href":230},"docs.desktop.search","Desktop conversational search docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fconversational-search",{"key":232,"label":233,"href":234},"docs.desktop.drive","Desktop drive docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fdesktop\u002Fdrive",{"key":236,"label":237,"href":86},"docs.desktop.download","Desktop download docs",{"key":239,"label":240,"href":241},"docs.piecesOS.overview","PiecesOS overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies",{"key":243,"label":244,"href":245},"docs.piecesOS.details","PiecesOS details docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os",{"key":247,"label":248,"href":154},"docs.piecesOS.install","PiecesOS install docs",{"key":250,"label":251,"href":252},"docs.piecesOS.quickMenu","PiecesOS quick menu docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Fquick-menu",{"key":254,"label":255,"href":256},"docs.piecesOS.storage","On-device storage docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fon-device-storage",{"key":258,"label":259,"href":260},"docs.piecesOS.troubleshooting","PiecesOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fcore-dependencies\u002Fpieces-os\u002Ftroubleshooting",{"key":262,"label":263,"href":264},"docs.mcp.overview","MCP overview docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp",{"key":266,"label":267,"href":268},"docs.mcp.cursor","MCP Cursor docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcursor",{"key":270,"label":271,"href":272},"docs.mcp.vscode","MCP VS Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fvs-code",{"key":274,"label":275,"href":276},"docs.mcp.claudeDesktop","MCP Claude Desktop docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-desktop",{"key":278,"label":279,"href":280},"docs.mcp.claudeCode","MCP Claude Code docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-code",{"key":282,"label":283,"href":284},"docs.mcp.claudeCowork","MCP Claude Cowork docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fclaude-cowork",{"key":286,"label":287,"href":288},"docs.mcp.githubCopilot","MCP GitHub Copilot docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgithub-copilot",{"key":290,"label":291,"href":292},"docs.mcp.goose","MCP Goose docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoose",{"key":294,"label":295,"href":296},"docs.mcp.windsurf","MCP Windsurf docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fwindsurf",{"key":298,"label":299,"href":300},"docs.mcp.zed","MCP Zed docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fzed",{"key":302,"label":303,"href":304},"docs.mcp.jetbrains","MCP JetBrains docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fjetbrains-ides",{"key":306,"label":307,"href":308},"docs.mcp.continueDev","MCP Continue docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcontinue-dev",{"key":310,"label":311,"href":312},"docs.mcp.cline","MCP Cline docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fcline",{"key":314,"label":315,"href":316},"docs.mcp.raycast","MCP Raycast docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fraycast",{"key":318,"label":319,"href":320},"docs.mcp.rovoDevCli","MCP Rovo Dev CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Frovo-dev-cli",{"key":322,"label":323,"href":324},"docs.mcp.openaiCodexCli","MCP OpenAI Codex CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenai-codex-cli",{"key":326,"label":327,"href":328},"docs.mcp.googleGeminiCli","MCP Google Gemini CLI docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fgoogle-gemini-cli",{"key":330,"label":331,"href":332},"docs.mcp.amazonQ","MCP Amazon Q docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Famazon-q-developer",{"key":334,"label":335,"href":336},"docs.mcp.chatgptDev","MCP ChatGPT Developer Mode docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fchatgpt-developer-mode",{"key":338,"label":339,"href":340},"docs.mcp.openclaw","MCP OpenClaw docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fopenclaw",{"key":342,"label":343,"href":344},"docs.mcp.mcpRemote","MCP Remote docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fmcp-remote",{"key":346,"label":347,"href":348},"docs.mcp.ngrok","MCP ngrok docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmcp\u002Fngrok-setup",{"key":350,"label":351,"href":352},"docs.troubleshooting.macOS","macOS troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fmacos",{"key":354,"label":355,"href":356},"docs.troubleshooting.windows","Windows troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Fwindows",{"key":358,"label":359,"href":360},"docs.troubleshooting.linux","Linux troubleshooting docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fmeet-pieces\u002Ftroubleshooting\u002Flinux",{"key":362,"label":363,"href":364},"docs.privacy","Privacy and security docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fprivacy-security-your-data",{"key":366,"label":367,"href":368},"docs.support","Support docs","https:\u002F\u002Fdocs.pieces.app\u002Fproducts\u002Fsupport",{"key":370,"label":371,"href":372},"portal.home","Pieces portal","https:\u002F\u002Fportal.pieces.app",{"key":374,"label":375,"href":376},"site.home","Website home","https:\u002F\u002Fpieces.app",{"key":378,"label":379,"href":380},"site.about","About page","https:\u002F\u002Fpieces.app\u002Fabout",{"key":382,"label":383,"href":384},"site.features","Features page","https:\u002F\u002Fpieces.app\u002Ffeatures",{"key":386,"label":387,"href":388},"site.plugins","Plugins page","https:\u002F\u002Fpieces.app\u002Fplugins",{"key":390,"label":391,"href":392},"site.contact","Contact page","https:\u002F\u002Fpieces.app\u002Fcontact",{"key":394,"label":36,"href":395},"site.changelog","https:\u002F\u002Fpieces.app\u002Fchangelog",{"key":397,"label":398,"href":399},"site.news","News","https:\u002F\u002Fpieces.app\u002Fnews",{"key":401,"label":402,"href":403},"site.events","Community events","https:\u002F\u002Fpieces.app\u002Fcommunity\u002Fevents",{"key":405,"label":406,"href":407},"site.userStories","User stories","https:\u002F\u002Fpieces.app\u002Fuser-stories",{"key":409,"label":410,"href":411},"site.academy","Academy","https:\u002F\u002Fpieces.app\u002Flearn\u002Facademy",{"key":413,"label":414,"href":415},"site.support","Website support","https:\u002F\u002Fpieces.app\u002Fsupport",{"key":417,"label":418,"href":419},"site.standup","Standup","https:\u002F\u002Fpieces.app\u002Fstandup",{"key":421,"label":33,"href":422},"site.blog","https:\u002F\u002Fcode.pieces.app\u002Fblog",{"key":424,"label":51,"href":425},"social.discord","https:\u002F\u002Fdiscord.gg\u002Fgetpieces",{"key":427,"label":54,"href":428},"social.x","https:\u002F\u002Fx.com\u002Fgetpieces",{"key":430,"label":431,"href":428},"social.twitter","Twitter",{"key":433,"label":434,"href":435},"social.instagram","Instagram","https:\u002F\u002Fwww.instagram.com\u002Fgetpieces\u002F",{"key":437,"label":438,"href":439},"social.tiktok","TikTok","https:\u002F\u002Fwww.tiktok.com\u002F@getpieces",{"key":441,"label":442,"href":443},"social.linkedin","LinkedIn","https:\u002F\u002Fwww.linkedin.com\u002Fcompany\u002Fgetpieces\u002F",{"key":445,"label":446,"href":447},"social.youtube","YouTube","https:\u002F\u002Fyoutube.com\u002F@getpieces",{"key":449,"label":450,"href":451},"github.org","GitHub organization","https:\u002F\u002Fgithub.com\u002Fpieces-app",{"key":453,"label":454,"href":455},"github.support","GitHub support","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport",{"key":457,"label":458,"href":459},"github.issues","GitHub issues","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fissues",{"key":461,"label":462,"href":463},"github.discussions","GitHub discussions","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fsupport\u002Fdiscussions",{"key":465,"label":466,"href":467},"github.documentation","GitHub documentation","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fdocumentation",{"key":469,"label":470,"href":471},"github.opensource","GitHub open source","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fopensource",{"key":473,"label":474,"href":475},"github.sdks.python","Python SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-python",{"key":477,"label":478,"href":479},"github.sdks.typescript","TypeScript SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-typescript",{"key":481,"label":482,"href":483},"github.sdks.dart","Dart SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-dart",{"key":485,"label":486,"href":487},"github.sdks.kotlin","Kotlin SDK","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fpieces-os-client-sdk-for-kotlin",{"key":489,"label":490,"href":491},"github.plugins.obsidian","Obsidian plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fobsidian-pieces",{"key":493,"label":494,"href":194},"github.plugins.jupyterlab","JupyterLab plugin repository",{"key":496,"label":497,"href":498},"github.plugins.sublime","Sublime plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_sublime",{"key":500,"label":501,"href":502},"github.plugins.neovim","Neovim plugin repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fplugin_neovim",{"key":504,"label":505,"href":506},"github.cliAgent","CLI agent repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fcli-agent",{"key":508,"label":509,"href":510},"github.mcpDart","MCP Dart repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fmcp_dart",{"key":512,"label":513,"href":514},"github.awesomePieces","Awesome Pieces repository","https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fawesome-pieces",{"key":516,"label":517,"href":518},"legal.privacyPolicy","Privacy policy","https:\u002F\u002Fpieces.app\u002Flegal\u002Fprivacy-policy",{"key":520,"label":521,"href":522},"legal.refundPolicy","Refund policy","https:\u002F\u002Fpieces.app\u002Flegal\u002Frefund-policy",{"key":524,"label":525,"href":526},"legal.terms","Terms","https:\u002F\u002Fpieces.app\u002Flegal\u002Fterms",{"key":528,"label":529,"href":530},"legal.security","Legal security","https:\u002F\u002Fpieces.app\u002Flegal\u002Fsecurity",{"key":532,"label":533,"href":447},"videos.youtubeChannel","YouTube channel",{"key":535,"label":536,"href":537},"videos.gettingStartedDesktop","Getting started desktop video","https:\u002F\u002Fyoutu.be\u002FdUr1lRM_TYk",{"key":539,"label":540,"href":541},"videos.snippetDiscovery","Snippet discovery video","https:\u002F\u002Fyoutu.be\u002FG6vb1USw-30",{"key":543,"label":544,"href":545},"sales.bookACall","Book a sales call","https:\u002F\u002Fcalendar.app.google\u002FWVUDtUfNy5Vst3sH7",{"key":547,"label":548,"href":549},"sales.enterprise","Enterprise form","https:\u002F\u002Fgetpieces.typeform.com\u002Fto\u002FaVQFTvpE",{"key":551,"label":552,"href":463},"sales.feedback","Feedback discussions",{"key":554,"label":555,"href":556},"sales.earlyAccess","Early access form","https:\u002F\u002Fgetpieces.typeform.com\u002Fearlyaccess",{"key":558,"label":559,"href":560},"sales.supportEmail","Support email","mailto:support@pieces.app",{"key":562,"label":563,"href":564},"routes.home","Home route","\u002F",{"key":566,"label":567,"href":46},"routes.about","About route",{"key":569,"label":570,"href":16},"routes.downloads","Downloads route",{"key":572,"label":573,"href":574},"routes.pricing","Pricing route","\u002Fpricing",{"key":576,"label":577,"href":578},"routes.security","Security route","\u002Fsecurity",{"key":580,"label":581,"href":49},"routes.enterprise","Enterprise route",{"key":583,"label":584,"href":585},"routes.thankYou","Thank you \u002F download route","\u002Fthank-you",{},"data\u002Fshared\u002Furls","P27xKEauu8D-8sfyr0wR4giF0teFSaCuAQ8kgcICQdI",{"id":590,"title":591,"author":592,"authorPhoto":593,"authorPhotoAlt":594,"authorSlug":595,"body":596,"buttonText":1360,"buttonUrl":1361,"category":1362,"date":1363,"description":1364,"draft":1365,"editorsPick":20,"extension":1366,"featured":1365,"image":1367,"imageAlt":594,"meta":1368,"navigation":20,"ogImage":1367,"ogImageAlt":594,"path":1369,"seo":1370,"stem":1371,"tags":594,"__hash__":1372},"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":597,"value":598,"toc":1332},"minimark",[599,622,627,630,652,656,659,674,677,695,699,706,711,716,719,723,730,740,745,751,758,762,765,771,774,778,781,795,798,804,811,815,818,824,831,842,845,849,852,858,861,865,872,875,881,884,888,891,897,902,905,911,914,918,921,927,930,934,937,943,947,950,956,960,966,972,976,979,985,989,992,998,1001,1005,1008,1014,1017,1021,1028,1035,1038,1043,1049,1054,1064,1070,1073,1076,1080,1083,1089,1092,1097,1103,1108,1125,1130,1147,1151,1165,1176,1192,1198,1207,1215,1219,1222,1242,1246,1249,1281,1284,1321,1324],[600,601,602,603,610,611,616,617,621],"p",{},"Welcome to Part 3! In ",[604,605,609],"a",{"href":606,"rel":607},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fbuilding-daily-standup-generator-with-pieces-api-sdk",[608],"nofollow","Part 1",", we built real-time sync with Pieces OS. In ",[604,612,615],{"href":613,"rel":614},"https:\u002F\u002Fpieces.app\u002Fblog\u002Fbuilding-pieces-productivity-app-with-gemini-ai",[608],"Part 2",", we added Gemini AI to extract insights. Now it's time to make it ",[618,619,620],"strong",{},"beautiful"," with a Flutter UI! 🎨",[623,624,626],"h2",{"id":625},"what-were-building","What we're building",[600,628,629],{},"A clean Flutter app that shows:",[631,632,633,637,640,643,646,649],"ul",{},[634,635,636],"li",{},"📅 Daily recap cards",[634,638,639],{},"💼 Projects with status indicators",[634,641,642],{},"👥 People you collaborated with (with avatars!)",[634,644,645],{},"⏰ Reminders",[634,647,648],{},"📝 Notes and learnings",[634,650,651],{},"🔄 \"Load More\" pagination",[623,653,655],{"id":654},"the-challenge","The challenge",[600,657,658],{},"We have two services working perfectly:",[631,660,661,668],{},[634,662,663,667],{},[664,665,666],"code",{},"PiecesOSService"," - Real-time data sync",[634,669,670,673],{},[664,671,672],{},"DailyRecapService"," - AI-powered insights",[600,675,676],{},"Now we need to:",[678,679,680,683,686,689,692],"ol",{},[634,681,682],{},"Initialize both services when the app starts",[634,684,685],{},"Load recaps for recent days",[634,687,688],{},"Display them in beautiful cards",[634,690,691],{},"Handle loading states",[634,693,694],{},"Allow pagination (\"Load More\")",[623,696,698],{"id":697},"setting-up","Setting up",[600,700,701,702,705],{},"First, add the avatar package to ",[664,703,704],{},"pubspec.yaml",":",[600,707,708],{},[664,709,710],{},"dependencies:",[600,712,713],{},[664,714,715],{},"flutter_initicon: ^2.0.0  # For generating avatars from names",[600,717,718],{},"This package creates beautiful avatars from initials - perfect for showing collaborators!",[623,720,722],{"id":721},"integrating-the-services","Integrating the services",[600,724,725,726,729],{},"Start by removing anything that may be in your ",[664,727,728],{},"main.dart"," file and replace it with:",[731,732,737],"pre",{"className":733,"code":735,"language":736},[734],"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",[664,738,735],{"__ignoreMap":739},"",[600,741,742,743,705],{},"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 ",[664,744,728],{},[731,746,749],{"className":747,"code":748,"language":736},[734],"\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",[664,750,748],{"__ignoreMap":739},[600,752,753,754,757],{},"We use a ",[664,755,756],{},"LoadingState"," enum to track different states - loading, healthy (success), or various error conditions. This gives us better control over the UI.",[623,759,761],{"id":760},"the-initialization-dance","The initialization dance",[600,763,764],{},"This is where it all comes together:",[731,766,769],{"className":767,"code":768,"language":736},[734]," \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",[664,770,768],{"__ignoreMap":739},[600,772,773],{},"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.",[623,775,777],{"id":776},"loading-recaps","Loading recaps",[600,779,780],{},"For each visible day, we:",[678,782,783,786,789,792],{},[634,784,785],{},"Fetch summaries with their content (from annotations)",[634,787,788],{},"Send the summaries to Gemini for analysis",[634,790,791],{},"Store the summaries in memory (for this session)",[634,793,794],{},"Update the UI",[600,796,797],{},"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.",[731,799,802],{"className":800,"code":801,"language":736},[734]," \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",[664,803,801],{"__ignoreMap":739},[600,805,806,807,810],{},"Notice we store recaps in memory (",[664,808,809],{},"_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!)",[623,812,814],{"id":813},"the-wrap-layout-responsive-cards","The wrap layout — responsive cards",[600,816,817],{},"Here's where the UI shines! Cards flow horizontally and wrap to the next row when they don't fit:",[731,819,822],{"className":820,"code":821,"language":736},[734],"\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",[664,823,821],{"__ignoreMap":739},[600,825,826,827,830],{},"The ",[664,828,829],{},"Wrap"," widget is perfect for this! It:",[631,832,833,836,839],{},[634,834,835],{},"Places cards horizontally first (left to right)",[634,837,838],{},"When a card doesn't fit, wraps to the next row",[634,840,841],{},"Handles different screen sizes automatically",[600,843,844],{},"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!",[623,846,848],{"id":847},"the-load-more-feature","The \"Load more\" feature",[600,850,851],{},"Let’s build the iconic “load more” button in our UI:",[731,853,856],{"className":854,"code":855,"language":736},[734]," \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",[664,857,855],{"__ignoreMap":739},[600,859,860],{},"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.",[623,862,864],{"id":863},"building-the-daily-recap-card","Building the Daily Recap Card",[600,866,867,868,871],{},"Here's where the magic happens. We take the ",[664,869,870],{},"DailyRecapData"," from our service and transform it into a beautiful card:",[600,873,874],{},"Let’s add a formatter function to format the date",[731,876,879],{"className":877,"code":878,"language":736},[734],"\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",[664,880,878],{"__ignoreMap":739},[600,882,883],{},"The card we just added is responsive and shows different sections based on what data is available.",[623,885,887],{"id":886},"clean-bullet-point-style","Clean bullet-point style",[600,889,890],{},"For readability, I kept it simple - no nested cards, just clean bullet points:",[731,892,895],{"className":893,"code":894,"language":736},[734],"\\\\ 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",[664,896,894],{"__ignoreMap":739},[898,899,901],"h3",{"id":900},"reminders-notes","Reminders & notes",[600,903,904],{},"Next, let’s add a reminders and notes widget!",[731,906,909],{"className":907,"code":908,"language":736},[734]," \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",[664,910,908],{"__ignoreMap":739},[600,912,913],{},"No nested cards, no complexity. Just clean, readable bullet points with icons!",[623,915,917],{"id":916},"people-avatars-with-initicon","People avatars with initicon",[600,919,920],{},"This is one of my favorite touches - generating avatars from names:",[731,922,925],{"className":923,"code":924,"language":736},[734],"\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",[664,926,924],{"__ignoreMap":739},[600,928,929],{},"Each person gets a unique color based on their name's hash. Looks great and no need for actual images!",[623,931,933],{"id":932},"project-status-indicators","Project status indicators",[600,935,936],{},"Projects show their status with icons and colored badges:",[731,938,941],{"className":939,"code":940,"language":736},[734]," \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",[664,942,940],{"__ignoreMap":739},[623,944,946],{"id":945},"notes-sections-the-new-addition","Notes sections — the new addition",[600,948,949],{},"Let’s add notes and reminders section to show important learnings from the day:",[731,951,954],{"className":952,"code":953,"language":736},[734],"\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",[664,955,953],{"__ignoreMap":739},[623,957,959],{"id":958},"handling-loading-states","Handling loading states",[600,961,962,963,965],{},"Good UX means showing what's happening. We use the ",[664,964,756],{}," enum to render different screens:",[731,967,970],{"className":968,"code":969,"language":736},[734],"\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",[664,971,969],{"__ignoreMap":739},[898,973,975],{"id":974},"the-loading-screen","The loading screen",[600,977,978],{},"Now, let’s build a loading screen!",[731,980,983],{"className":981,"code":982,"language":736},[734],"  \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",[664,984,982],{"__ignoreMap":739},[898,986,988],{"id":987},"the-error-screen","The error screen",[600,990,991],{},"Inevitably, we’ll hit some errors. Let’s build a screen for that!",[731,993,996],{"className":994,"code":995,"language":736},[734],"  \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",[664,997,995],{"__ignoreMap":739},[600,999,1000],{},"Now users get specific, actionable error messages instead of generic failures!",[898,1002,1004],{"id":1003},"loading-individual-cards","Loading individual cards",[600,1006,1007],{},"Next, let’s get the cards loading in individually:",[731,1009,1012],{"className":1010,"code":1011,"language":736},[734],"  \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",[664,1013,1011],{"__ignoreMap":739},[600,1015,1016],{},"This keeps the users in the loop, so they know exactly what’s going on!",[623,1018,1020],{"id":1019},"the-macos-network-permission-issue","The macOS network permission issue",[600,1022,1023,1024,1027],{},"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 ",[664,1025,1026],{},"localhost"," (your own computer).",[600,1029,1030,1031,1034],{},"When you try to connect to Pieces OS at ",[664,1032,1033],{},"localhost:39300",", macOS blocks it because your app doesn't have permission to make network connections.",[600,1036,1037],{},"You can fix this by:",[678,1039,1040],{},[634,1041,1042],{},"Find these two files in your project:",[600,1044,1045,1046],{},"   - ",[664,1047,1048],{},"macos\u002FRunner\u002FDebugProfile.entitlements",[600,1050,1045,1051],{},[664,1052,1053],{},"macos\u002FRunner\u002FRelease.entitlements",[678,1055,1057],{"start":1056},2,[634,1058,1059,1060,1063],{},"Open each file and add these two lines inside the ",[664,1061,1062],{},"\u003Cdict>"," section:",[731,1065,1068],{"className":1066,"code":1067,"language":736},[734]," \u003Ckey>com.apple.security.network.client\u003C\u002Fkey>\n \u003Ctrue\u002F>\n",[664,1069,1067],{"__ignoreMap":739},[600,1071,1072],{},"   3. Save both files.",[600,1074,1075],{},"That's it! Your app can now connect to Pieces OS and other network services.",[623,1077,1079],{"id":1078},"running-our-application","Running our application",[600,1081,1082],{},"Which sets up your workspace for your device, then you can run:",[731,1084,1087],{"className":1085,"code":1086,"language":736},[734],"flutter run -d macos --dart-define=GEMINI_API_KEY=your-key-here\n",[664,1088,1086],{"__ignoreMap":739},[600,1090,1091],{},"And boom! You get:",[600,1093,1094],{},[618,1095,1096],{},"On Startup:",[731,1098,1101],{"className":1099,"code":1100,"language":736},[734],"Connecting to Pieces OS...\nThis may take a few seconds\n",[664,1102,1100],{"__ignoreMap":739},[600,1104,1105],{},[618,1106,1107],{},"After Loading:",[631,1109,1110,1113,1116,1119,1122],{},[634,1111,1112],{},"3 beautiful daily recap cards",[634,1114,1115],{},"Cards flow horizontally, wrap to next row if needed",[634,1117,1118],{},"Each card 400px wide, responsive to screen size",[634,1120,1121],{},"Each showing summary, projects, people, reminders, notes",[634,1123,1124],{},"\"Load More Days\" button at the bottom",[600,1126,1127],{},[618,1128,1129],{},"When You Click \"Load More Days\":",[631,1131,1132,1135,1138,1141,1144],{},[634,1133,1134],{},"Shows loading spinner",[634,1136,1137],{},"Fetches 3 more days",[634,1139,1140],{},"Generates their recaps with Gemini",[634,1142,1143],{},"Adds them to the wrap layout",[634,1145,1146],{},"Seamless expansion!",[623,1148,1150],{"id":1149},"missing-icons","Missing icons?",[600,1152,1153,1154,1157,1158,1157,1161,1164],{},"If you find that the icons (like ",[664,1155,1156],{},"Icons.summarize",", ",[664,1159,1160],{},"Icons.alarm",[664,1162,1163],{},"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.",[678,1166,1167],{},[634,1168,1169,1172,1173,1175],{},[618,1170,1171],{},"Open"," ",[664,1174,704],{},".",[600,1177,1178,1172,1181,1172,1184,1187,1188,1191],{},[618,1179,1180],{},"Ensure the",[664,1182,1183],{},"uses-material-design: true",[618,1185,1186],{},"line is present and uncommented"," under the ",[664,1189,1190],{},"flutter:"," section. This line explicitly tells Flutter to include the Material Icons font in your application bundle.",[731,1193,1196],{"className":1194,"code":1195,"language":736},[734],"# Inside pubspec.yaml\nflutter:\n  uses-material-design: true\n",[664,1197,1195],{"__ignoreMap":739},[600,1199,1200,1172,1203,1206],{},[618,1201,1202],{},"Run",[664,1204,1205],{},"flutter pub get"," in your terminal after confirming or adding the line to fetch dependencies and update the project configuration.",[678,1208,1209],{},[634,1210,1211,1214],{},[618,1212,1213],{},"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.",[623,1216,1218],{"id":1217},"what-weve-built","What we've built",[600,1220,1221],{},"At this point, you have a fully functional productivity that:",[631,1223,1224,1227,1230,1233,1236,1239],{},[634,1225,1226],{},"✅ Syncs with Pieces OS in real-time",[634,1228,1229],{},"✅ Uses AI to generate daily insights",[634,1231,1232],{},"✅ Displays beautiful, responsive cards",[634,1234,1235],{},"✅ Handles loading and error states",[634,1237,1238],{},"✅ Supports pagination with \"Load More\"",[634,1240,1241],{},"✅ Shows projects, people, reminders, and notes",[623,1243,1245],{"id":1244},"but-theres-room-for-improvement","But there's room for improvement...",[600,1247,1248],{},"Right now, there are a few limitations:",[678,1250,1251,1257,1263,1269,1275],{},[634,1252,1253,1256],{},[618,1254,1255],{},"Every restart regenerates all recaps"," - Slow and wastes API calls",[634,1258,1259,1262],{},[618,1260,1261],{},"Can't refresh a single recap"," - Stuck with what you got",[634,1264,1265,1268],{},[618,1266,1267],{},"Error handling could be better"," - Just console logs",[634,1270,1271,1274],{},[618,1272,1273],{},"No tests"," - Hard to maintain with confidence",[634,1276,1277,1280],{},[618,1278,1279],{},"No CI\u002FCD"," - Manual testing every time",[600,1282,1283],{},"In Part 4, we'll tackle all of these! We'll add:",[631,1285,1286,1293,1300,1307,1314],{},[634,1287,1288,1289,1292],{},"💾 ",[618,1290,1291],{},"Persistent caching with Hive"," - Instant loads on restart",[634,1294,1295,1296,1299],{},"🔄 ",[618,1297,1298],{},"Regenerate button"," - Refresh any recap on demand",[634,1301,1302,1303,1306],{},"❌ ",[618,1304,1305],{},"Better error handling"," - Beautiful error cards with retry",[634,1308,1309,1310,1313],{},"✅ ",[618,1311,1312],{},"Automated tests"," - Catch bugs before they ship",[634,1315,1316,1317,1320],{},"🚀 ",[618,1318,1319],{},"CI\u002FCD pipeline"," - Automated testing and builds",[600,1322,1323],{},"See you in Part 4! 🚀",[600,1325,1326,1331],{},[604,1327,1330],{"href":1328,"rel":1329},"https:\u002F\u002Fgithub.com\u002Fpieces-app\u002Fblog-dart-daily-stand-up-generator",[608],"Reference GitHub"," to view the full project.",{"title":739,"searchDepth":1056,"depth":1056,"links":1333},[1334,1335,1336,1337,1338,1339,1340,1341,1342,1343,1347,1348,1349,1350,1355,1356,1357,1358,1359],{"id":625,"depth":1056,"text":626},{"id":654,"depth":1056,"text":655},{"id":697,"depth":1056,"text":698},{"id":721,"depth":1056,"text":722},{"id":760,"depth":1056,"text":761},{"id":776,"depth":1056,"text":777},{"id":813,"depth":1056,"text":814},{"id":847,"depth":1056,"text":848},{"id":863,"depth":1056,"text":864},{"id":886,"depth":1056,"text":887,"children":1344},[1345],{"id":900,"depth":1346,"text":901},3,{"id":916,"depth":1056,"text":917},{"id":932,"depth":1056,"text":933},{"id":945,"depth":1056,"text":946},{"id":958,"depth":1056,"text":959,"children":1351},[1352,1353,1354],{"id":974,"depth":1346,"text":975},{"id":987,"depth":1346,"text":988},{"id":1003,"depth":1346,"text":1004},{"id":1019,"depth":1056,"text":1020},{"id":1078,"depth":1056,"text":1079},{"id":1149,"depth":1056,"text":1150},{"id":1217,"depth":1056,"text":1218},{"id":1244,"depth":1056,"text":1245},"Get started","https:\u002F\u002Fpieces.app\u002F","AI & LLM","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",{},"\u002Fblog\u002Fbuilding-pieces-productivity-with-flutter-ui",{"title":591,"description":1364},"blog\u002Fbuilding-pieces-productivity-with-flutter-ui","jp2gsWWou1B2IfIugHuXNOP3O5oOACpDEH1GcwAxphE",{"id":1374,"title":592,"body":1375,"description":1379,"draft":1365,"extension":1366,"meta":1382,"navigation":20,"path":1383,"photo":1384,"photoAlt":594,"seo":1385,"stem":1386,"__hash__":1387},"authors\u002Fauthors\u002Fbishoy-hany.md",{"type":597,"value":1376,"toc":1380},[1377],[600,1378,1379],{},"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":739,"searchDepth":1056,"depth":1056,"links":1381},[],{},"\u002Fauthors\u002Fbishoy-hany","https:\u002F\u002Fstorage.googleapis.com\u002Fpieces-marketing-website\u002Fimages\u002Fauthors\u002Fbishoy-hany.jpg",{"title":592,"description":1379},"authors\u002Fbishoy-hany","_m1q8mqNk1I4j1V0_YJSK0zuMS48DBT4Nqw9e3PZjMM"]