[{"data":1,"prerenderedAt":301},["ShallowReactive",2],{"navigation_docs":3,"-docs-architecture-overview":59,"-docs-architecture-overview-surround":296},[4,25,40],{"title":5,"path":6,"stem":7,"children":8,"page":24},"Getting Started","\u002Fdocs\u002Fgetting-started","docs\u002F1.getting-started",[9,14,19],{"title":10,"path":11,"stem":12,"icon":13},"Introduction","\u002Fdocs\u002Fgetting-started\u002Fintroduction","docs\u002F1.getting-started\u002F1.introduction","i-lucide-house",{"title":15,"path":16,"stem":17,"icon":18},"Installation","\u002Fdocs\u002Fgetting-started\u002Finstallation","docs\u002F1.getting-started\u002F2.installation","i-lucide-download",{"title":20,"path":21,"stem":22,"icon":23},"Using Porter","\u002Fdocs\u002Fgetting-started\u002Fusing-porter","docs\u002F1.getting-started\u002F3.using-porter","i-lucide-terminal",false,{"title":26,"path":27,"stem":28,"children":29,"page":24},"Architecture","\u002Fdocs\u002Farchitecture","docs\u002F2.architecture",[30,35],{"title":31,"path":32,"stem":33,"icon":34},"Overview","\u002Fdocs\u002Farchitecture\u002Foverview","docs\u002F2.architecture\u002F1.overview","i-lucide-layers",{"title":36,"path":37,"stem":38,"icon":39},"Receiver internals","\u002Fdocs\u002Farchitecture\u002Freceiver","docs\u002F2.architecture\u002F2.receiver","i-lucide-smartphone",{"title":41,"path":42,"stem":43,"children":44,"page":24},"Reference","\u002Fdocs\u002Freference","docs\u002F3.reference",[45,49,54],{"title":46,"path":47,"stem":48,"icon":23},"Commands","\u002Fdocs\u002Freference\u002Fcommands","docs\u002F3.reference\u002F1.commands",{"title":50,"path":51,"stem":52,"icon":53},"Wire format","\u002Fdocs\u002Freference\u002Fwire-format","docs\u002F3.reference\u002F2.wire-format","i-lucide-binary",{"title":55,"path":56,"stem":57,"icon":58},"Decisions","\u002Fdocs\u002Freference\u002Fdecisions","docs\u002F3.reference\u002F3.decisions","i-lucide-file-text",{"id":60,"title":31,"body":61,"description":289,"extension":290,"links":291,"meta":292,"navigation":293,"path":32,"seo":294,"stem":33,"__hash__":295},"docs\u002Fdocs\u002F2.architecture\u002F1.overview.md",{"type":62,"value":63,"toc":283},"minimark",[64,68,79,82,87,94,202,213,217,223,229,244,256,260,271,277,280],[65,66,67],"p",{},"Porter is two independent programs that never communicate except through a\ncamera pointed at a screen.",[69,70,76],"pre",{"className":71,"code":73,"language":74,"meta":75},[72],"language-text","Sending machine                        Receiving device\n  (Rust binary)                          (Flutter app)\n\n  read file\n  chunk it\n  encode (base64 \u002F gzip \u002F fountain)\n  build QR frames\n  render slideshow  ──── light ────►   camera\n                                        decode QR\n                                        parse frame\n                                        assemble (worker isolate)\n                                        verify SHA-256 → save\n","text","",[77,78,73],"code",{"__ignoreMap":75},[65,80,81],{},"Everything below the arrow happens with no knowledge of the sender's state, and\neverything above it happens with no knowledge of the receiver's. There is no\nhandshake and no acknowledgement.",[83,84,86],"h2",{"id":85},"sender","Sender",[65,88,89,90,93],{},"A single static Rust binary, ",[77,91,92],{},"rust-sender\u002F",".",[95,96,97,110],"table",{},[98,99,100],"thead",{},[101,102,103,107],"tr",{},[104,105,106],"th",{},"Module",[104,108,109],{},"Responsibility",[111,112,113,124,134,144,154,164,174,188],"tbody",{},[101,114,115,121],{},[116,117,118],"td",{},[77,119,120],{},"cli.rs",[116,122,123],{},"Hand-rolled flag parsing — ~15 simple flags don't warrant a dependency",[101,125,126,131],{},[116,127,128],{},[77,129,130],{},"chunker.rs",[116,132,133],{},"Splits the file, builds sequential frames, derives the transfer id",[101,135,136,141],{},[116,137,138],{},[77,139,140],{},"fountain.rs",[116,142,143],{},"LT-code symbol generation: PRNG, degree table, XOR",[101,145,146,151],{},[116,147,148],{},[77,149,150],{},"qrtypes.rs",[116,152,153],{},"QR version\u002Fcapacity sizing and ECC levels",[101,155,156,161],{},[116,157,158],{},[77,159,160],{},"renderer.rs",[116,162,163],{},"Draws QR codes as terminal cells",[101,165,166,171],{},[116,167,168],{},[77,169,170],{},"tui.rs",[116,172,173],{},"The ratatui interface — grid, sidebar, status line, keybindings",[101,175,176,181],{},[116,177,178],{},[77,179,180],{},"serve.rs",[116,182,183,184,187],{},"The ",[77,185,186],{},"axum"," HTTP receiver",[101,189,190,195],{},[116,191,192],{},[77,193,194],{},"join.rs",[116,196,197,198,201],{},"Reassembles ",[77,199,200],{},".partXX"," files with a SHA-256 check",[65,203,204,205,208,209,212],{},"The TUI choice drove the language choice: ",[77,206,207],{},"ratatui","\u002F",[77,210,211],{},"crossterm"," is what the\ninteractive controls — scrubbing, jump-to-chunk, gap-fill — are built on.",[83,214,216],{"id":215},"receiver","Receiver",[65,218,219,220,93],{},"A Flutter app for Android and macOS, ",[77,221,222],{},"flutter\u002F",[69,224,227],{"className":225,"code":226,"language":74,"meta":75},[72],"camera → mobile_scanner → ChunkParser → worker isolate → ProgressSnapshot → UI\n                                          Assembler\n                                          FountainDecoder\n                                          ChunkStorage\n",[77,228,226],{"__ignoreMap":75},[65,230,231,232,235,236,239,240,243],{},"The important boundary is the worker isolate. It owns the ",[77,233,234],{},"Assembler",", the\n",[77,237,238],{},"FountainDecoder"," and all chunk and metadata disk I\u002FO for the app's lifetime;\nonly small ",[77,241,242],{},"ProgressSnapshot"," values cross back to the UI isolate.",[65,245,246,247,251,252,255],{},"That shape is deliberate — see ",[248,249,250],"a",{"href":56},"the decisions"," for why\na per-call ",[77,253,254],{},"compute()"," was the wrong answer.",[83,257,259],{"id":258},"sizing","Sizing",[65,261,262,263,266,267,270],{},"QR capacity is a function of terminal height, which means ",[77,264,265],{},"K"," and ",[77,268,269],{},"blockSize","\nare computed per run rather than fixed:",[69,272,275],{"className":273,"code":274,"language":74,"meta":75},[72],"version   = clamp(((rows - buffer) * 2 - 17 - 4) \u002F 4, 1, 40)\ncapacity  = byte-mode capacity for (version, ecc)\nblockSize = floor((capacity - headerReserve) * 0.75)\nK         = ceil(fileSize \u002F blockSize)\n",[77,276,274],{"__ignoreMap":75},[65,278,279],{},"A bigger terminal means a higher version, more bytes per frame, and fewer\nframes. It also means resizing mid-transfer forks the stream into something the\nreceiver cannot merge with what it already has.",[65,281,282],{},"The Rust sender computes the header reserve exactly and forces a single\nByte-mode QR segment, because the QR crate's \"optimal\" segmentation can charge\nmore bits than the byte-mode capacity tables assume — a payload under the table\nlimit could otherwise still be rejected.",{"title":75,"searchDepth":284,"depth":284,"links":285},2,[286,287,288],{"id":85,"depth":284,"text":86},{"id":215,"depth":284,"text":216},{"id":258,"depth":284,"text":259},"The two programs, the one-way link between them, and where each stage of a transfer happens.","md",null,{},{"icon":34},{"title":31,"description":289},"y184pTbmf8WNkoDCVgEsHXhDshVCyb5YPnYli1ftC30",[297,299],{"title":20,"path":21,"stem":22,"description":298,"icon":23,"children":-1},"Choosing an encoding, driving the sender, and the three ways to get a file back.",{"title":36,"path":37,"stem":38,"description":300,"icon":39,"children":-1},"The worker isolate that owns decoding, and the disk hydration that makes a transfer resumable.",1786373778113]