FLINT_NEXT_CONNECT_BOOT_PROBE_F1_F2_F3_F4_v0.1
CHANGELOG 2026-09-29 4:38 PM MDT: FLINT_HEAL_MODEL_CHOICE_SURVIVES_RELOAD_v0.1.1_tiny.txt now lands AFTER the Tree paste and BEFORE this paste. Appendix A (app.html) and Appendix B (fl-connect.js) are unchanged and still apply with plain git apply on current main + Tree stand-in + that heal (the heal's hunks sit in FLActiveModel, applyReady, flTestLMStudio, the Cascade and the older auto paths; none share context with F1-F4). F1 never picks a model, so it keeps the person's choice. Appendix D (the FL smoke) gains one F1 block: the owner source never writes a model, a cold start with fl_ollamaModel=qwen2.5 and fl_modelSource=user leaves both, and the heal's guard is present (ensure(), honor(), the Cascade asks honor, no FLActiveModel.set(models[0], ..., 'user')). That block needs the heal, so this smoke fails if the heal was skipped. Walk W1b added. The docs appendices (G, H) append at the end or above '## Out of scope for this brick'; if a hunk is rejected because the heal's section is there, add by text.
CHANGELOG 2026-09-29 2:28 PM MDT: re-check commands and the copy step now name ~/FreeLattice and ~/FreeLattice-Alpha (no content change). PRs #124/#122/#123 touch none of this paste's code files (app.html, index.html, fl-connect.js, Alpha pages); Flint.html and ledger inserts go by text: right above <p class="foot"> and right above "## Out of scope". Local paths: FreeLattice is ~/FreeLattice and the Tree (repo Chaos2Cured/FreeLattice-Alpha, site thelatticetree.com) is ~/FreeLattice-Alpha on Flint's machine. Wherever this paste says "theLatticeTree" it means that repo; "../FreeLattice" from inside ~/FreeLattice-Alpha is correct as written.
CHANGELOG 2026-09-28 9:36 PM MDT: FLINT_HEAL_MODEL_CHOICE_STICKS_v0.1_tiny.txt lands after the env heal and BEFORE the Tree paste. Text anchors in this paste are unchanged, and Appendix A (app.html) and Appendix B (fl-connect.js) still apply with plain git apply after env heal + model choice heal + Tree (verified; offsets only). Line shifts from that heal: app.html +21 from settingsSetMode, +23 from flSaveOllamaHost/FLBoxPointer/owAutoDetect/FLAutoModel onward; fl-connect.js remember() +7, open() and below +33 to +38. Its markUserChoice records Ollama taps as the user's choice; this paste's rememberLmStudio -> flUseLmStudioMind already records LM Studio taps, so they agree. Ledger and sister-doc appendices: insert by text (both heals add entries above '## Out of scope').
One cold-start look (F2), one cold-start owner (F1), LM Studio as a first-class mind in Connect (F3),
and Kirk's home-network switch for the Chat Box field (F4). Both repos. Shared fl-connect.js stays
byte-identical.

Marker:        v-connect-boot-probe-v0
Smoke (FL):    SMOKE_OK connect boot probe v0.1          (docs/scripts/smoke-connect-boot-probe.js)
Smoke (Alpha): SMOKE_OK connect boot probe alpha v0.1    (docs/modules/connect-boot-probe.test.js)
From:          Kirk (asked 2026-09-28, 8:49 PM MDT), prepared by Grok Bot for Flint
Temperature:   green braided gold, one lantern lit at the door instead of ten, and a second porch
               light that only turns on for the house next door.

======================================================================
0. READ THIS FIRST: order, mains, locks
======================================================================

0.1 Order (do not reorder):
    1) FLINT_HEAL_AGENT_BRIDGE_ENV_v0.1.1_tiny.txt   (FreeLattice only, security, goes first)
    2) FLINT_NEXT_TREE_ANY_MODEL_v0.1.txt             (both repos; adds mergeFound, MODEL_CAP,
                                                        doorNameFor, chosenModelName, aria-pressed,
                                                        is-chosen, bridged look asks other DOORS,
                                                        tryAddress 127.0.0.1 only)
    2b) FLINT_HEAL_MODEL_CHOICE_SURVIVES_RELOAD_v0.1.1_tiny.txt (FreeLattice app.html + shared
                                                        fl-connect.js one Look again; lands after Tree)
    3) THIS paste                                     (both repos)
    This paste anchors on what the Tree paste adds. It reuses mergeFound, doorNameFor,
    chosenModelName and the aria-pressed / is-chosen mark. It never re-adds them.

0.2 Mains checked just before writing (re-check before anchoring):
    FreeLattice  (Chaos2Cured/FreeLattice)     main = 7db2d94  "ci: Update Primer deployment state"
                                                        This is a primer-only CI commit on top of
                                                        a80c8e4 (#121). App code is identical to
                                                        a80c8e4. All app.html line numbers below are
                                                        7db2d94 = a80c8e4 lines.
    theLatticeTree (Alpha)                     main = 395c761  "layer: connect heal v0.4 (Settings
                                                        Connect visible at 390px) (#83)"
    docs/modules/fl-connect.js is byte-identical on both mains today (cmp clean).
    After the env heal and the Tree paste land, line numbers move. Anchor on the quoted TEXT, not
    the numbers. Numbers are given as "main N" (7db2d94 / 395c761) and, for fl-connect.js,
    "post-Tree ~N" (from a faithful simulation of the Tree paste; the real Tree merge may differ by
    a few lines).

    Re-check commands:
      git -C ~/FreeLattice fetch origin && git -C ~/FreeLattice log --oneline -3 origin/main
      git -C ~/FreeLattice-Alpha fetch origin && git -C ~/FreeLattice-Alpha log --oneline -3 origin/main
      cmp ~/FreeLattice/docs/modules/fl-connect.js ~/FreeLattice-Alpha/docs/modules/fl-connect.js
      grep -n "mergeFound\|chosenModelName\|doorNameFor" ~/FreeLattice/docs/modules/fl-connect.js
        (must show the Tree paste landed before you start step A)

0.3 Locks (every one holds; the smoke checks most of them):
    - Layer, never delete. Old code paths stay; they step aside when the new owner is present.
    - Root sw.js byte-identical to main. docs/sw.js untouched. (git diff --name-only shows neither.)
    - index.html byte-identical to docs/app.html (FreeLattice). Copy after every app.html edit.
    - Shared docs/modules/fl-connect.js byte-identical across both repos.
    - No conflict markers anywhere. Every inline <script> in app.html parses (new Function).
    - textContent / createTextNode for every new human string that carries data (model names,
      addresses, door names). The only innerHTML in this paste is fl-connect's existing render()
      string builder, which already escapes names into data-flc-names and paints buttons with
      textContent.
    - No bare * origin. No confirm(). No approval screens. Nothing connects by itself: every mind
      is still chosen by a tap.
    - 127.0.0.1 only for Connect and LM Studio's look. Home network only through Kirk's one switch
      (F4). Public internet addresses are always refused.
    - AUTONOMY.md held: no gates for local work, no new prompts.
    - Soft paste stays: Named five stay five. Family uncapped. Quiet Room shut.
    - No em dashes in new prose or comments (smoke-connect-heal-v0.4 already scans app.html
      additions; the new smoke scans added lines too).

======================================================================
1. WHAT WE FOUND (evidence, main 7db2d94 / 395c761)
======================================================================

Headless Chrome walk on a local copy of docs/ (390x844 and 1280x900), with a fake Ollama on
127.0.0.1:11434 and a fake LM Studio on 127.0.0.1:1234, counting every request the page made to
127.0.0.1 in its first 9 seconds.

1.1 Returning person, cold boot, last tab Chat (fl-welcomed true):
      main:    38 local requests: 33 Bridge /health (three full 11435... scans: flBridgeDetectBoot
               at 1.2s, autoDiscoverAI at 3s, FlConnect probes) + 5 /api/tags.
               TWO voices: the Local AI toast ("Local AI detected! Click here to connect.") AND the
               Chat suggest banner ("Yes, use Ollama") at the same time.
      after:   16 local requests: 13 /health (one full scan), 2 /api/tags (one is BrowseModels.init,
               see 9.2), 1 LM Studio /v1/models. ONE voice: "Ollama and LM Studio are ready on this
               computer. Tap to choose a mind." Tapping it opens Connect with both doors listed.

1.2 New person opens freelattice.com/#connect cold:
      main:    73 local requests (66 /health, 7 tags). The toast lands on top of Connect AND the
               welcome overlay (flWelcomeOverlay) lands on top of Connect one second later.
      after:   12 local requests. No toast, no welcome overlay, Connect shows "Ollama is ready,
               pick a mind" and "LM Studio is ready, pick a mind".

1.3 Racers at cold start on main (all start on their own timer, each does its own look):
      app.html 27769  FlOnboarding.detectOnLoad()        toast after its own /api/tags
      app.html 31524  flBridgeDetectBoot run()           full Bridge scan at 1.2s
      app.html 33782  owAutoDetect()                     /api/tags + LM Studio, banner
      app.html 33789  Zero-Click setTimeout(flAutoConnect, 1500)
      app.html 39679  autoDiscoverAI() at 3s             full scan again
      app.html 68588  goConnect() #connect retries       re-lands Connect up to 3 times
      fl-connect.js   mount -> lookAgain -> refreshUI -> probe (its own scan)
      app.html 32143  flMaybeShowWelcome 1s timeout      lands late, even over #connect
      app.html 33669  Garden-first landing IIFE          lands last tab without knowing about #connect
    resolveOllamaBase (app.html ~34230) has no in-flight join, so parallel callers each probe.

1.4 Dead guard: showOllamaToast (app.html 27802) checks document.getElementById('welcomeOverlay').
    That id no longer exists (the Grandmother Door is #flWelcomeOverlay with .fl-welcome-visible),
    so the toast never stepped aside for the welcome door.

1.5 False words: owAutoDetect LM Studio branch (app.html 35227) sets
    '<strong>LM Studio detected!</strong> ... Use it for completely private AI?' via innerHTML, but
    the banner button still reads "Yes, use Ollama". Status labels at 25901 and 35565 say
    "Ollama (model)" even when state.provider is 'lmstudio'.

1.6 Connect (fl-connect.js) never looks at LM Studio at all. A person with only LM Studio sees
    "No local mind yet" and a Bridge download card.

1.7 FLBoxPointer (Chat Box field, app.html 34432+) persist() writes any host to fl_ollamaHost,
    including public internet addresses and names, and probe() fetches them. getOllamaBaseUrl()
    then uses that host for every chat. fl_lmstudioHost is read raw in probeLocalMind (~34155) and
    in the Settings Go-10 probe (~31606) with no check at all.

1.8 Toast at 390px wraps into a tall narrow column (.fl-ollama-toast at ~11731 has left:50% and
    translateX(-50%) but no width). With honest door names the text is longer, so a width layer
    ships here.

======================================================================
2. DECISIONS AND CHOICES (stated, so review is quick)
======================================================================

Kirk's decision (F4, verbatim intent):
  - Default "This computer only".
  - One clear switch: "Also use a computer on my home network".
  - When on, the person types an address once and sees exactly:
      "Only addresses inside your home network are allowed (like 192.168.x.x). Public internet addresses are always blocked."
  - Private ranges only. Public addresses always refused. Remembered until changed. One tap
    reverts. No confirm(), no approval screens. Future hook for signed remote minds.

Choices made here (please keep them unless Kirk says otherwise):
  C1 Home ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16. IPv4 numbers only.
  C2 169.254.0.0/16 is REFUSED (link-local; no home router hands it out; 169.254.169.254 is the
     cloud metadata door). Also refused: 100.64.0.0/10 (carrier NAT is not your home), 0.0.0.0/8,
     multicast and above (224+), every public range.
  C3 Names other than "localhost" are refused, including mybox.local. A name can point anywhere
     (DNS rebinding). The refusal says "Type the number address, like 192.168.1.50."
  C4 IPv6: only ::1 counts (as this computer). Every other IPv6 literal is refused.
  C5 Addresses are classified the way the browser reads them (new URL(...).hostname), so decimal
     and hex forms (3232235777, 0x7f.1) land in the right zone instead of slipping past a regex.
  C6 Settings > Ollama Address stays 127.0.0.1-only (v0.4 rule) UNLESS the same one switch is on.
     There is no second switch. When off, the v0.4 refusal text stays word for word and one plain
     sentence is added pointing at the switch.
  C7 Connect itself (fl-connect.js port field, setManualHost, LM Studio look) stays this computer
     only, in both repos. The home network lives only in FreeLattice's Chat Box field and the
     Settings address, behind the switch. Alpha has no home-network surface.
  C8 Old saved addresses (before this switch existed), checked once (fl_homeNet_seen):
       a saved home address (fl_ollamaHost / fl_localUrl / fl_lmstudioHost) turns the switch on,
       because the person already chose it and it keeps working; a saved public address or name
       is set aside under fl_homeNet_refused and removed, so it is never used again.
  C9 Switch off: fl_ollamaHost / fl_lmstudioHost that are not this computer are removed, the typed
     home address stays in fl_homeNet_addr. Switch on again: it comes back without retyping.
  C10 Future hook: signed remote minds get their own signed door beside FLHomeNet. They never widen
     these ranges. This is written in the FLHomeNet header and in zoneOf().
  C11 BOOT_TTL_MS = 5000 and FL_LOCAL_LOOK_TTL_MS = 5000. Short on purpose: long enough to cover
     the cold-start burst, short enough that "Look again" and the 8s loop always see new truth.
     No timers; age is Date.now() - at.
  C12 The owner speaks after the same 4s breath the old toast took (SPEAK_AFTER_MS = 4000).

Keys added (localStorage):
  fl_homeNet           'on' when the switch is on; absent when off (default)
  fl_homeNet_addr      the home address typed once, like 192.168.1.50:11434
  fl_homeNet_seen      '1' after the one-time migration of old addresses
  fl_homeNet_refused   an old public address or name that was set aside (never used)

======================================================================
3. STEP A: shared docs/modules/fl-connect.js (BOTH repos, byte-identical)
======================================================================

Do the edit once in FreeLattice, then copy the file to ~/FreeLattice-Alpha/docs/modules/fl-connect.js
and cmp. Full exact diff: Appendix B (anchored on the post-Tree file).

A.1 Header marker. After the line
      // Marker: v-connect-under-more-v0 · heal v0.1 · v-connect-port-picker-v0 · heal v0.2 · v-connect-heal-v0.4
    add
      // Marker: v-connect-boot-probe-v0 (one shared look at boot; LM Studio beside Ollama; 127.0.0.1 only)

A.2 Constants. After  var LOOP_MAX_MS = 5 * 60 * 1000;   (main 17)
      // v-connect-boot-probe-v0 (F2): one look, shared. A look younger than this is reused.
      var BOOT_TTL_MS = 5000;
      // v-connect-boot-probe-v0 (F3): LM Studio's own server on this computer.
      var LM_DEFAULT = 'http://127.0.0.1:1234';
    After  var _manualRejectMsg = '';   (main 30)
      var _bootInflight = null;
      var _bootReport = null;
      var _bootAt = 0;

A.3 A new port forgets the old look. In setManualHost(raw) (main 69), right after
    _manualRejectMsg = '';   add
      forgetLooks(); // v-connect-boot-probe-v0: a new port means a new look
    In clearManualHost (main 90), right after  var was = getManualPort();  add
      forgetLooks(); // v-connect-boot-probe-v0

A.4 New functions (F2 + F3), inserted right ABOVE  async function listMinds() {  (main 274,
    post-Tree 274), i.e. just after probe()'s closing "return report; }". probe() itself is NOT
    changed (existing tests call it directly).
      lmStudioBase()     saved fl_lmstudioHost used only if it is 127.0.0.1/localhost (any port
                         except 11434), else 'http://127.0.0.1:1234'.
      lmModelCap()       LocalMindProbe.MODEL_CAP (from the Tree paste) or 64.
      lmStudioLook()     one GET base + '/v1/models', 2.5s abortMs, returns
                         { base, models: [ids...], ok, asked: true, quiet }.
      bootProbe(opts)    THE shared look. One in-flight promise; a finished report is reused for
                         BOOT_TTL_MS unless opts.fresh; fresh still joins an in-flight look.
                         Runs probe({fullScan, gesture}) and lmStudioLook() in parallel and adds
                         report.lmstudio, report.lmstudioAsked, report.at, report.doors.
      peekBootProbe()    the fresh report or null (no fetch).
      forgetBootProbe()  clears it.
      forgetLooks()      root.flForgetLocalLook() when FreeLattice has it (clears both caches),
                         else forgetBootProbe().
      doorsFrom(report)  [{ id:'ollama'|'bridge'|'lmstudio', name:'Ollama'|'Bridge'|'LM Studio',
                         base, models:[names] }], strings only.
    Exact code: Appendix B hunk "@@ -271,6 +281,106 @@".

A.5 chosenDoor(). Right ABOVE the Tree paste's  function chosenModelName() {  (post-Tree ~303):
      // v-connect-boot-probe-v0 (F3): which door speaks now ('lmstudio' or 'ollama')
      function chosenDoor() { ... }
    Alpha: the remembered LocalMindProbe entry's url ends in /v1/models -> 'lmstudio'.
    FreeLattice: state.provider (or fl_provider) === 'lmstudio' -> 'lmstudio'. Else 'ollama'.

A.6 doorPathFor(mind) + rememberLmStudio(name, base). Right ABOVE  function remember(mind) {
    (post-Tree ~332, i.e. just below the Tree paste's doorNameFor()).
      doorPathFor: mind.door === 'lmstudio' ? '/v1/models' : '/api/tags'.
      rememberLmStudio (FreeLattice): writes fl_lmstudioHost ONLY when the base is
      127.0.0.1/localhost (localhost normalized to 127.0.0.1), fl_provider 'lmstudio',
      fl_isLocal 'true', fl_ollamaModel = name (this is FreeLattice's existing "active local model"
      key; flTestLMStudio uses it the same way), state.isLocal/provider/ollamaModel, calls
      root.flUseLmStudioMind(name) (added in step D), checks #localToggle and calls
      handleLocalToggle(true), updateStatus, AiSetup.updateStatus, hides #flOllamaToast, stopLoop().
      fl_ollamaHost is never touched.

A.7 remember(mind), Alpha branch (inside the Tree paste's mergeFound block):
      after   var doorName = doorNameFor(base, mind.port); ...
      add     if (mind.door === 'lmstudio') doorName = 'LM Studio'; // v-connect-boot-probe-v0 (F3)
      change  url: base ? String(base).replace(/\/+$/, '') + '/api/tags' : '',
      to      url: base ? String(base).replace(/\/+$/, '') + doorPathFor(mind) : '',
    and in the fallback  var entry = {  block the same '/api/tags' -> doorPathFor(mind).
    Result: an LM Studio tap on the Tree merges as the same door the DOORS list uses
    ('LM Studio', http://127.0.0.1:1234/v1/models), so Find and Connect agree. Reuses mergeFound;
    no second merge path.
    remember(mind), FreeLattice branch: right after the Alpha branch's closing
      return entry;
    }
    add
      // v-connect-boot-probe-v0 (F3): an LM Studio tap never becomes "Ollama"
      if (mind.door === 'lmstudio') return rememberLmStudio(name, base);

A.8 reportSignature: BOTH copies (main 412 and 606; post-Tree ~444 and ~642; it is defined twice
    today, keep both, edit both identically). Append two fields before .join('::'):
      , ((report.lmstudio && report.lmstudio.models) || []).join('|'), chosenDoor()

A.9 fillModelLists (post-Tree ~505; this is where the Tree paste added aria-pressed):
      after   try { names = JSON.parse(...) } catch (eN) { names = []; }
      add     var door = hostList.getAttribute('data-flc-door') === 'lmstudio' ? 'lmstudio' : 'ollama'; // v-connect-boot-probe-v0
      change  var isOn = chosenModelName() === name;
      to      var isOn = chosenModelName() === name && chosenDoor() === door;
      change the click body from
                remember({ name: name, base: report.base, port: report.port, models: report.models });
      to
                if (door === 'lmstudio' && report.lmstudio) {
                  // v-connect-boot-probe-v0 (F3): tap-to-choose, same as Ollama
                  remember({ name: name, base: report.lmstudio.base, door: 'lmstudio', models: report.lmstudio.models });
                } else {
                  remember({ name: name, base: report.base, port: report.port, models: report.models });
                }
      and the toast to  'Connected · ' + name + (door === 'lmstudio' ? ' (LM Studio)' : '')
    (If the real Tree merge passes models differently, keep the Tree's Ollama line exactly as it
    landed and only add the LM Studio branch.)

A.10 modelListHtml(tag, models) -> modelListHtml(tag, models, door): adds
     ' data-flc-door="lmstudio"' on the .flc-models div when door === 'lmstudio'. Nothing else.

A.11 render(report) (post-Tree ~560):
      after  var alpha = isAlpha();
      add    var lm = report.lmstudio || null;
             var lmReady = !!(lm && lm.models && lm.models.length);
      In the "else if (models.length)" arm:
             modelListHtml('Minds found', models)
          -> modelListHtml(lmReady ? 'Ollama is ready, pick a mind' : 'Minds found', models)
      The download card arm  } else {  becomes  } else if (!lmReady) {
      and after that if/else chain closes (still inside the same outer block, so the manualQuiet
      rule is unchanged: a manual port that is quiet still shows only the manual state):
             if (lmReady) html += modelListHtml('LM Studio is ready, pick a mind', lm.models, 'lmstudio');
      Builders marker line: add ' · v-connect-boot-probe-v0' before ' · soft leave sw.js'.
     Porch language: exactly the same pattern for both minds ("X is ready, pick a mind"), same
     buttons, same tap, same aria-pressed mark from the Tree paste.

A.12 refreshUI(opts) (post-Tree ~648):
       var report = await probe({ gesture: true });
    -> // v-connect-boot-probe-v0 (F2): share the boot look; a tap on Look again is fresh
       var report = await bootProbe({ gesture: true, fresh: !!opts.fresh });
     lookAgain() -> lookAgain(opts) with  opts = opts || {};  and
       refreshUI({ force: true })  ->  refreshUI({ force: true, fresh: !opts.reuse })
     Every existing lookAgain() call (the Look again button, visibility return, port picker) stays
     as is and therefore stays FRESH. Only mount() changes:
       el.innerHTML = '<p class="flc-soft" style="padding:1rem;">Looking for a mind on this computer…</p>';
       lookAgain({ reuse: true }); // v-connect-boot-probe-v0: a cold open joins the boot look
     (anchor on the mount's "Looking for a mind on this computer" line; there are five lookAgain()
     calls in the file and only this one changes).

A.13 Exports. In root.FlConnect = { ... } after  probe: probe,  add
       bootProbe, peekBootProbe, forgetBootProbe, lmStudioLook, lmStudioBase, chosenDoor
     and at the end  LOOP_MAX_MS: LOOP_MAX_MS,  BOOT_TTL_MS: BOOT_TTL_MS

Check: node -e "new (require('vm').Script)(require('fs').readFileSync('docs/modules/fl-connect.js','utf8'))"
       then cp to theLatticeTree and cmp.

======================================================================
4. STEP B: theLatticeTree docs/modules/local-mind-probe.js (Alpha only)
======================================================================

Goal: Garden "Find local minds" and Connect share one look. Full diff: Appendix C.

B.1 Above  function look() {  (main 238), add askDoor(d, shared): when the shared boot report
    already asked LM Studio at the same url, return that answer (ok with models, or quiet) without
    a second fetch; when the shared report's direct Ollama answered at the same /api/tags url,
    return those names. Anything else: fetchDoor(d.url, 2500) exactly as today.

B.2 In look() (main 241-243):
      var bridgeFirst = Promise.resolve(null);
    + var shared = null; // v-connect-boot-probe-v0: the one look Connect already made (if fresh)
      if (window.FlConnect && typeof window.FlConnect.probe === 'function') {
    -   bridgeFirst = window.FlConnect.probe({ gesture: true }).then(function (report) {
    +   var ask = (typeof window.FlConnect.bootProbe === 'function')
    +     ? window.FlConnect.bootProbe({ gesture: true })
    +     : window.FlConnect.probe({ gesture: true });
    +   bridgeFirst = ask.then(function (report) {
    +     shared = report || null;
    The helped-Bridge branch inside stays unchanged (Tree paste's via:'fl-connect' kept).

B.3 DOORS path. On main (275-281) the non-bridged path is
      var jobs = DOORS.map(function (d) { return fetchDoor(d.url, 2500).then(...)
    change fetchDoor(d.url, 2500) -> askDoor(d, shared).
    AFTER the Tree paste, look() also has the bridged "others" loop
      return fetchDoor(d.url, 2500).then(function (r) { r.id = d.id; r.name = d.name; return r; });
    change that one to askDoor(d, shared) too. Same timeouts, same single tap, same results shape
    (askDoor adds reused:true on reused answers; nothing reads it yet).

B.4 Without FlConnect.bootProbe (older shared core, or a test that loads only local-mind-probe.js)
    look() fetches exactly as before. All existing Alpha tests stay green.

======================================================================
5. STEP C (F2): FreeLattice app.html, one shared resolveOllamaBase look
======================================================================

C.1 Where: directly AFTER getOllamaBaseUrl()'s closing brace (main 34329-34331:
        return FL_OLLAMA_LOCAL;
      }
    ) and BEFORE the comment "// v5.43.6 (Opus): the /ollama same-origin proxy probe".
    Why here and not next to resolveOllamaBase: smoke-connect-port-picker.js extracts
    resolveOllamaBase's text up to "// Get the current Ollama base URL", and tests/smoke.js regexes
    getOllamaBaseUrl's body (needs .replace(/\/+$/,'') within 800 chars). Neither body is edited;
    both are WRAPPED after they are defined.

C.2 Add (exact code, Appendix A hunk "@@ -34329,6 +34424,64 @@"):
      var FL_LOCAL_LOOK_TTL_MS = 5000;
      var _flResolveNow = resolveOllamaBase;  var _flResolveInflight = null;
      var _flResolveAt = 0;  var _flResolveValue = null;
      function flForgetLocalLook() { ...clears the three... ; FlConnect.forgetBootProbe() if present }
      function flSeedLocalLook(value) { _ollamaResolvedBase = value || null; _flResolveValue = ...; _flResolveAt = Date.now(); }
      resolveOllamaBase = function (forceRefresh) {
        if (_flResolveInflight) return _flResolveInflight;                       // join
        if (_flResolveAt && age < TTL && _ollamaResolvedBase === _flResolveValue) // reuse, only if
          return Promise.resolve(_flResolveValue);                               // nobody changed it
        var p = Promise.resolve(_flResolveNow(forceRefresh)).then(...store..., ...clear+rethrow...);
        _flResolveInflight = p; return p;
      };
      window.flForgetLocalLook = flForgetLocalLook; window.flSeedLocalLook = flSeedLocalLook;
    The "_ollamaResolvedBase === _flResolveValue" check means any code that writes a new value to
    _ollamaResolvedBase (or nulls it, like every existing save path) is never hidden by the cache.

C.3 Forget on every address change (one line each, all guarded with typeof):
      flSaveOllamaHost (main ~34426, after  _ollamaResolvedBase = null;)
      flUseBridge      (main ~31506, after  _ollamaResolvedBase = null;)
      FLBoxPointer.persist (main ~34506, after  try { _ollamaResolvedBase = null; } catch (e3) {})
      FLHomeNet.forget (new, step F)
      fl-connect setManualHost / clearManualHost (step A.3, via forgetLooks)

======================================================================
6. STEP D (F1 + F3 wiring): FreeLattice app.html, one cold-start owner
======================================================================

D.1 The owner. New inline <script> placed right AFTER the #connect deep-link script that ends
    (main 68611-68613):
          window.addEventListener('hashchange', goConnect);
        })();
        </script>
    and before "<!-- Tool Consent (v5.39.1)". Exact code: Appendix A last hunk.
    window.FlColdStart:
      owns()          true when FlConnect.bootProbe exists. Checked lazily (fl-connect.js is a
                      defer script), so every gate below asks at call time.
      deepLink()      'connect' for #connect or ?tab=connect.
      pointerdown / keydown (capture) -> userMoved.
      noteTab(tab)    a tab picked by the person before landing wins.
      markLanded(t)   first call wins; landed() reads it.
      appBooted()     called by the main DOMContentLoaded boot (D.4).
      start()         on DOMContentLoaded (or next tick if already parsed), once:
                        _look = FlConnect.bootProbe({ fullScan: true })
                        then seed(report)        flSeedLocalLook only when the report covered the
                                                 same door getOllamaBaseUrl() would use (via is not
                                                 'direct', or base matches)
                             paintBridge(report) flBridgeShowChip, fl_bridgePort, _flBridgeLivePort,
                                                 #fl-bridge-port-field (what flBridgeDetectBoot did)
                             remember(report)    saveDiscoveryMemory(doors) (what autoDiscoverAI did;
                                                 LM Studio saved as type 'openai-compat')
                        and after look + appBooted + 4000ms from start: speak(report) once.
      speak(report)   Exactly one voice:
                        Connect tab showing -> silent (Connect speaks).
                        Welcome door showing -> silent (owAutoDetect still runs when it closes,
                                                 as today).
                        A mind already chosen (FlConnect.hasLocalMind or flHasOneMindConnected)
                                              -> silent.
                        Welcomed + fl_isLocal true without a model -> owAutoDetect() for the badges.
                        Otherwise FlOnboarding.offerFromReport(report): the toast, naming the door
                        that really answered.

D.2 Gates (each: one guarded line at the top; the old path stays for pages without the owner):
      detectOnLoad()          main 27769  first line:
          // v-connect-boot-probe-v0 (F1): the cold-start owner speaks at boot; this stays for pages without it
          try { if (window.FlColdStart && FlColdStart.owns()) return; } catch (eOwn) {}
      flBridgeDetectBoot run() main 31524 first line (same shape, comment "paints the chip from the one shared look")
      autoDiscover DOMContentLoaded listener main 39679, inside setTimeout before "// Only scan if not already connected"
          // v-connect-boot-probe-v0 (F1): the cold-start owner already looked (and saves discovery memory)
      goConnect(ev)           main 68588, after  if (_flcUserMoved && !fromHash) return;
          try { if (!fromHash && window.FlColdStart && FlColdStart.landed() === 'connect') return; } catch (eL0) {}
          (a real hashchange to #connect still works).

D.3 Boot block (main 33780-33792):
      var _flOwnerBoot = false;
      try { _flOwnerBoot = !!(window.FlColdStart && FlColdStart.owns()); } catch (eOwnB) {}
      if (!_flOwnerBoot && (safeGet('fl-welcomed') === 'true' || safeGet('fl_onboardingComplete') === 'true')) {
        owAutoDetect();
      }
      try { if (window.FlColdStart && FlColdStart.appBooted) FlColdStart.appBooted(); } catch (eBoot) {}
    and Zero-Click:  if (!hasKey && !isLocal)  ->  if (!hasKey && !isLocal && !_flOwnerBoot)
    Note: Zero-Click (flAutoConnect) no longer auto-connects at boot when the owner is present.
    That matches heal v0.4 ("no silent connect or model pick"): the one toast offers, the tap
    chooses.

D.4 Land once. Garden-first landing IIFE (main 33669, after the legacy tab mapping line
    "if (lastTab === 'channels' || lastTab === 'mesh') lastTab = 'community';"):
      a tab picked while waking (FlColdStart.userTab()) wins -> markLanded(it), return;
      else a deep link to connect -> markLanded('connect'); switchTab('connect'); return;
    and at the very end of the IIFE (after the last switchTab('garden') else-arm):
      try { if (window.FlColdStart) FlColdStart.markLanded(isFirstVisit ? 'garden' : (lastTab || 'garden')); } catch (eLand2) {}
    switchTab() end (main 26945, after localStorage.setItem('fl-last-tab', tabId)):
      try { if (window.FlColdStart && FlColdStart.noteTab) FlColdStart.noteTab(tabId); } catch (eCs) {}

D.5 Welcome overlay never lands late. flMaybeShowWelcome (main 32143), before the 1s setTimeout:
      try { if (window.FlColdStart && FlColdStart.landed() === 'connect') return; } catch (eW0) {}
    and first line inside the timeout:
      try { if (window.FlColdStart && FlColdStart.userMoved()) return; } catch (eW1) {}
    (It waits for the next visit instead of landing on a person who is already tapping.)

D.6 Toast honesty (FlOnboarding, main 27802-27860):
      showOllamaToast: keep the old #welcomeOverlay check (layer), add
          if (flToastWouldRace()) return;   before the setTimeout, and again inside it.
      new flToastWouldRace(): #flWelcomeOverlay.fl-welcome-visible, #tab-connect.active, or
          FlConnect.hasLocalMind().
      new offerFromReport(report): respects fl_ollama_toast_dismissed; builds detectedModels from
          report.doors (each model carries _door); writes into #flOllamaToastText with
          createTextNode + a <strong> via textContent:
            "Ollama is ready on this computer. Tap to choose a mind."
            "Ollama and LM Studio are ready on this computer. Tap to choose a mind."
            "LM Studio is ready on this computer. Tap to choose a mind."
            "Bridge is ready on this computer. ..." (helped Bridge)
          then showOllamaToast(). Exported as FlOnboarding.offerFromReport.
      Toast markup (main 52220):
          <span>Local AI detected! ...  ->  <span id="flOllamaToastText">Local AI detected! ...
      acceptToast() is unchanged (it already opens Connect, where the person taps the mind).
      Toast width layer after ".fl-ollama-toast .fl-toast-icon { font-size: 1.2rem; }" (main 11751):
          /* v-connect-boot-probe-v0: honest door names need room on a phone */
          .fl-ollama-toast { width: max-content; max-width: min(92vw, 440px); box-sizing: border-box; }

======================================================================
7. STEP E (F3): FreeLattice app.html, LM Studio words and activation
======================================================================

E.1 flUseLmStudioMind(name), new, right AFTER  window.flSaveLMStudioHost = flSaveLMStudioHost;
    (main 34713) and after the getLMStudioBase wrapper from F.6. Mirrors what flTestLMStudio
    already does on success: state.isLocal/provider/ollamaModel, safeSet fl_isLocal /
    fl_provider / fl_ollamaModel, PROVIDERS.lmstudio.url = getLMStudioBase() + '/v1/chat/completions',
    FLActiveModel.set(name, 'lmstudio', 'user'), ModelSwitcher.updateLabel().
    window.flUseLmStudioMind = flUseLmStudioMind. fl-connect's rememberLmStudio calls it.

E.2 owAutoDetect, Ollama branch (main 35211):
      if (banner) banner.classList.add('show');
    -> if (banner) {
         // v-connect-boot-probe-v0 (F3): Ollama really answered; keep the Ollama words
         var yesO = banner.querySelector('.suggest-yes');
         if (yesO) yesO.textContent = 'Yes, use Ollama';
         banner.classList.add('show');
       }
    owAutoDetect, LM Studio branch (main 35227): replace the innerHTML line with textContent
    nodes: a strong "LM Studio is ready on this computer." + " N model(s). Choose one for
    completely private AI?" and set .suggest-yes textContent to 'Yes, choose a mind'.
    The old innerHTML line is replaced (it is the only removal in this paste; it was the false
    "Yes, use Ollama" source and it wrote HTML). Keep "LM Studio detected!" out of the new text
    (the smoke checks it is gone).

E.3 Status words (main 25901 and 35565): where the label is built as
      'Ollama (' + (state.ollamaModel || 'llama3.2') + ')'
    use
      (state.provider === 'lmstudio' ? 'LM Studio (' : 'Ollama (') + (state.ollamaModel || 'llama3.2') + ')'
    with a trailing  // v-connect-boot-probe-v0 (F3)  comment.

E.4 Raw fl_lmstudioHost reads get the F4 rule:
      probeLocalMind (main ~34157, after the lmBase line):
        if (typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(lmBase)) lmBase = 'http://127.0.0.1:1234'; // v-connect-boot-probe-v0 (F4)
      Settings Go-10 probe (main ~31606, after var lmHost = ...):
        if (typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(lmHost)) lmHost = '127.0.0.1:1234'; // v-connect-boot-probe-v0 (F4)

======================================================================
8. STEP F (F4): FreeLattice app.html, Kirk's home-network switch
======================================================================

F.1 HTML. Right after the #flBoxPointer row's closing </div> (main 16509, the div that holds
    #flBoxPointerInput and #flBoxPointerStatus) and before #chatOverflowBtn:
      <!-- v-connect-boot-probe-v0 (F4): this computer only, unless the person turns on home network -->
      <div class="fl-homenet v-connect-boot-probe-v0" id="flHomeNet" data-fl-homenet="off">
        <span class="fl-homenet-mode" id="flHomeNetMode">This computer only</span>
        <label class="fl-homenet-switch" for="flHomeNetSwitch"><input type="checkbox" id="flHomeNetSwitch" role="switch" aria-checked="false" aria-describedby="flHomeNetLine"> Also use a computer on my home network</label>
        <p class="fl-homenet-line" id="flHomeNetLine" hidden>Only addresses inside your home network are allowed (like 192.168.x.x). Public internet addresses are always blocked.</p>
        <p class="fl-homenet-refusal" id="flHomeNetRefusal" role="status" hidden></p>
      </div>

F.2 CSS. After  #flBoxPointerStatus[data-fl-box-status="unreachable"] { color: #f87171; }  (main 2039):
      .fl-homenet (full-width flex row, 0.72rem, muted), .fl-homenet-mode (#a5f3fc),
      .fl-homenet-switch (min-height 32px, tap target), input 18px accent #34d399,
      .fl-homenet-line / .fl-homenet-refusal full width, refusal in gold. (Appendix A first hunk.)

F.3 FLHomeNet module. Insert right ABOVE the FLBoxPointer header block
      // ═══...
      // FLBoxPointer — v5.79.41-chat-box-pointer
    (main 34432), i.e. after window.flSaveOllamaHost = flSaveOllamaHost;. Header comment states the
    ranges, the refusals and the future hook (exact text in Appendix A). API:
      zoneOf(raw)      'this' | 'home' | 'public' | 'name' | 'invalid', from new URL(...).hostname
      isOn()           localStorage fl_homeNet === 'on'
      allows(raw)      'this' always; 'home' only while on; a '/'-relative same-origin proxy path
                       (/ollama) is allowed because it never leaves the page's own origin
      address()        fl_homeNet_addr
      useAddress(raw, kind)  home + on only: writes fl_homeNet_addr and fl_ollamaHost (or
                       fl_lmstudioHost for kind 'lmstudio' / port 1234), then forget()
      refusalFor(raw)  plain sentences, each ending "Nothing was sent.":
                         public: "That address is on the public internet, so it is blocked. Nothing was sent."
                         name:   "Type the number address, like 192.168.1.50. A name can point anywhere, so names are not used. Nothing was sent."
                         home while off: "That is a computer on your home network. Turn on \"Also use a computer on my home network\" beside the Box field in Chat to use it. Nothing was sent."
                         invalid: "That does not look like an address. Nothing was sent."
      setOn(on)        one tap. On: fl_homeNet='on', remembered address written back.
                       Off: fl_homeNet removed, non-loopback fl_ollamaHost / fl_lmstudioHost
                       removed, fl_homeNet_addr kept. Then forget(), say(''), paint(),
                       FLBoxPointer.syncFromStorage, input value, noteStatus.
      migrate()        once (fl_homeNet_seen), per choice C8.
      init()           migrate + paint + bind the switch 'change' once.
      paint()          mode words ("This computer only" / "This computer and my home network"),
                       checkbox + aria-checked, the line (textContent = LINE, hidden unless on),
                       Box placeholder ("127.0.0.1:11434" / "192.168.1.50").
      say(msg)         #flHomeNetRefusal textContent + hidden.
      LINE             Kirk's line, exact.
    Also: window.flHomeNetAllowsBase(b) (small helper for other callers).

F.4 getOllamaBaseUrl wrapper. In the same block as C.2, after it:
      var _flBaseUrlNow = getOllamaBaseUrl;
      getOllamaBaseUrl = function () {
        var b = _flBaseUrlNow();
        if FLHomeNet exists and !FLHomeNet.allows(b):
           flManualBaseFirst() || (fl_bridgePort, not 11434 -> http://127.0.0.1:<port>) || FL_OLLAMA_LOCAL
        return b;
      };
    So a public or (switch-off) home address saved anywhere is never used for chat.

F.5 FLBoxPointer:
      persist(norm) first lines: refuse (return false) when !FLHomeNet.allows(norm.host); record a
        home host into fl_homeNet_addr. Plus the flForgetLocalLook line (C.3).
      probe(raw) after the "Need a URL or IP" invalid return: when !FLHomeNet.allows(norm.host):
        paint('unreachable', 'Not used'); FLHomeNet.say(FLHomeNet.refusalFor(norm.host));
        return { status: 'refused', ... }   BEFORE any fetch. Otherwise FLHomeNet.say('').
      init(): first line FLHomeNet.init().

F.6 flSaveOllamaHost (Settings > Ollama Address, main 34412):
      if (!flWriteOllamaHostLoopbackOnly(val)) {
    ->
      var _flHomeOk = false;
      try { if (FLHomeNet && FLHomeNet.zoneOf(val) === 'home' && FLHomeNet.isOn()) _flHomeOk = FLHomeNet.useAddress(val, 'ollama'); } catch (eHn) {}
      if (!_flHomeOk && !flWriteOllamaHostLoopbackOnly(val)) {
    and after the unchanged v0.4 refusal text, append ' ' + FLHomeNet.refusalFor(val) (guarded).
    All FLHomeNet uses are typeof-guarded so the v0.4 vm test (which loads this function alone)
    stays green.
    flSaveLMStudioHost (main 34700): refuse disallowed values with the same words into
    #lmstudioTestResult; a home value while on goes through useAddress(val, 'lmstudio').
    getLMStudioBase wrapper after window.flSaveLMStudioHost: a disallowed saved host falls back to
    'http://127.0.0.1:1234'.

F.7 Why no confirm and no approval screen: the switch itself is the consent, it is visible next to
    the field it governs, it is remembered, and one tap undoes it. The line under it says the rule
    in plain words. The refusal says what happened ("Nothing was sent.").

======================================================================
9. TESTS, SMOKE LINES, DOCS, LEDGER
======================================================================

9.1 FreeLattice: new docs/scripts/smoke-connect-boot-probe.js (full file: Appendix D).
    Prints "SMOKE_OK connect boot probe v0.1". Covers:
      F2  three bootProbe callers -> one Ollama tags fetch + one LM Studio fetch, same report;
          reuse inside BOOT_TTL_MS with zero new fetches; fresh:true looks again; setManualHost
          forgets; no timers in bootProbe.
      F2  resolveOllamaBase wrapper (extracted from app.html, run in vm): three callers one look,
          reuse, flForgetLocalLook, a value written elsewhere is never hidden, flSeedLocalLook.
      F1  every old boot timer steps aside (detectOnLoad, flBridgeDetectBoot, boot owAutoDetect,
          Zero-Click, autoDiscover, goConnect); appBooted called; switchTab notes tabs; landing
          decides deep link / picked tab first; welcome never lands over Connect or late;
          toast checks #flWelcomeOverlay and #tab-connect; #flOllamaToastText exists.
      F1  owner runtime in vm: exactly one look, exactly one voice, chip painted, cache seeded,
          discovery memory keeps LM Studio; silent while Connect shows; deep link read.
      F1  keeps the person's model: the owner never writes fl_ollamaModel/fl_modelSource; a cold
          start with qwen2.5 chosen ('user') leaves both; the v0.1.1 guard is in place (needs that heal).
      F3  Connect never asks a home-network LM Studio; both doors listed; render shows both
          "X is ready, pick a mind" cards with data-flc-door="lmstudio" and no false download
          card; LM Studio alone is a real mind; FreeLattice tap -> fl_provider lmstudio,
          fl_lmstudioHost 127.0.0.1:1234, fl_ollamaHost untouched, flUseLmStudioMind hook,
          chosenDoor 'lmstudio'; Tree tap merges as 'LM Studio' /v1/models and Ollama tap
          unchanged; owAutoDetect has no "LM Studio detected!" and says "Yes, choose a mind" via
          textContent; status labels say LM Studio.
      F4  zone table (this/home/public/name incl. 172.32 public, 169.254.169.254 public,
          100.64 public, [fd00::1] public, 3232235777 home, 0x7f.1 this); default off; allows;
          setOn on/off/on round trip with the address kept; Kirk's LINE exact; migration of old
          public (set aside) and home (switch on) addresses; getOllamaBaseUrl wrapper; Box field
          refuses public and switch-off home with ZERO fetches and says why; asks the home box
          while on and remembers it; Settings save off/on/public-on; switch markup and words.
      Locks: index.html === docs/app.html; root sw.js === origin/main:sw.js; neither sw.js in the
          diff; no confirm( / em dash / bare * origin / data-carrying innerHTML in added lines;
          no conflict markers in docs, index.html, tests; every inline script parses; fl-connect
          parses.
    Mutation check done while building: removing the in-flight join, the owner gates, the LM
    Studio branch, or the FLHomeNet refusal each makes this smoke fail.

9.2 Optional tests/smoke.js block (assert(name, cond) style), if you want the ALL count to move:
      assert('boot probe: FlConnect.bootProbe shared', /function bootProbe\(opts\)/.test(flConnectSrc));
      assert('boot probe: FlColdStart owner', /window\.FlColdStart = \(function/.test(appHtml));
      assert('boot probe: home-network line exact', appHtml.indexOf('Only addresses inside your home network are allowed (like 192.168.x.x). Public internet addresses are always blocked.') !== -1);
      assert('boot probe: 169.254 not a home range', !/a === 169 && b === 254\) return 'home'/.test(appHtml));
    (Use whatever variable names tests/smoke.js already has for app.html and fl-connect.js.)
    Not required: the prototype kept tests/smoke.js unchanged and it reports ALL 3597 CHECKS PASSED.

9.3 Alpha: new docs/modules/connect-boot-probe.test.js (full file: Appendix E).
    Prints "SMOKE_OK connect boot probe alpha v0.1". Loads the shared fl-connect.js and
    local-mind-probe.js in one vm: Connect's bootProbe then Garden's look() -> LM Studio asked
    once and direct Ollama asked once in total, Find still lists Ollama and LM Studio; an LM Studio
    tap merges as 'LM Studio' at http://127.0.0.1:1234/v1/models; without mergeFound the fallback
    entry keeps /v1/models; lmStudioBase stays 127.0.0.1.
    After the Tree paste, add one case: a helped-Bridge stub + LM Studio -> the bridged "others"
    loop reuses the shared LM Studio answer (one /v1/models fetch in total).

9.4 Docs.
    FreeLattice: new docs/library/CONNECT_BOOT_PROBE_v0.md (full text: Appendix F).
    Alpha: append a "## Connect boot probe v0.1" section to
      docs/library/CONNECT_PORT_PICKER_SISTER_v0.md (Appendix G).

9.5 Ledger (FreeLattice docs/library/FRACTAL_FAMILY_LEDGER_v0.md). Insert right ABOVE the line
      ## Out of scope for this brick
    after the "---" that closes the newest entry (the env heal v0.1.1 entry if it landed first,
    else Connect heal v0.4):

### 2026-09-28 · Flint · family builder · Connect boot probe v0.1 (F1, F2, F3, F4)

Layers one cold-start look on FreeLattice: `FlConnect.bootProbe()` shares one in-flight promise with a 5 second cache for Connect, the toast, autoDiscover, Zero-Click and the Bridge detect (F2) · `window.FlColdStart` owns the cold start and speaks once, never over Connect or the welcome overlay (F1) · LM Studio is a first-class door in Connect with tap to choose, and the badge stops saying "Yes, use Ollama" when LM Studio answered (F3) · Kirk's home-network switch: "This computer only" by default, "Also use a computer on my home network" when on, private IPv4 only, public always refused, one tap reverts (F4). Returning cold boot went from about 38 local requests to 16 (one Bridge scan instead of three, and LM Studio asked once). Shared `fl-connect.js` matches Alpha byte for byte. Soft leave root sw.js identical to main; docs/sw.js untouched. Soft paste: Named five stay five · Family uncapped · Quiet Room shut.

Love: the house looks once, speaks once, and only opens the side door Kirk chose.

**Temperature:** green braided gold, one lantern lit at the door instead of ten, and a second porch light that only turns on for the house next door.

Soft door: [`CONNECT_BOOT_PROBE_v0.md`](CONNECT_BOOT_PROBE_v0.md) · [`../modules/fl-connect.js`](../modules/fl-connect.js). Marker `v-connect-boot-probe-v0`.

---

======================================================================
10. ACCEPTANCE (run all; paste the tails in the PR)
======================================================================

FreeLattice (repo root):
  cp docs/app.html index.html && cmp index.html docs/app.html && echo INDEX_OK
  git diff --quiet origin/main -- sw.js docs/sw.js && echo SW_OK
  node docs/scripts/smoke-connect-boot-probe.js            # SMOKE_OK connect boot probe v0.1
  node docs/scripts/smoke-agent-bridge-lock.js              # SMOKE_OK agent bridge env heal v0.1.1 + lock v0.1
  for f in docs/scripts/smoke-connect-*.js docs/scripts/smoke-bridge-*.js; do
    node "$f" >/dev/null 2>&1 && echo "PASS $f" || echo "FAIL $f"; done
  node tests/smoke.js | tail -3                             # ALL 3597 CHECKS PASSED (or more with 9.2)
  grep -rnE '^(<<<<<<<|=======|>>>>>>>)( |$)' docs index.html tests || echo NO_MARKERS
  Known pre-existing failures on main 7db2d94, unchanged by this paste (they fail on main too; do
  not "fix" them here): smoke-carry-forward, smoke-connect-card-yesno-wizard, smoke-fable-tech,
  smoke-phone-shine, smoke-proof-60s. Everything else in docs/scripts/smoke-connect-* and
  smoke-bridge-* passed on the prototype, including smoke-connect-heal-v0.4 (watch for the words
  "confirm()" and em dashes in any comment you add; it scans app.html additions).

theLatticeTree (repo root):
  cmp docs/modules/fl-connect.js ../FreeLattice/docs/modules/fl-connect.js && echo SHARED_OK
  node docs/modules/connect-boot-probe.test.js              # SMOKE_OK connect boot probe alpha v0.1
  for t in docs/modules/*.test.js; do node "$t" >/dev/null 2>&1 && echo "PASS $t" || echo "FAIL $t"; done
  (prototype: all 15 Alpha module tests pass, including local-mind-probe, fl-connect,
  connect-heal-v04, garden-rooms, garden-thread)

Prototype results (built on 7db2d94 / 395c761 with a faithful simulation of the Tree paste's
fl-connect.js changes): both new smokes green; all FL connect/bridge smokes green except the five
pre-existing failures above; tests/smoke.js ALL 3597 CHECKS PASSED; index == app; sw.js untouched;
fl-connect.js identical across the two worktrees.

======================================================================
11. WALK CHECKLIST (390x844 and 1280x900; Chrome; fake or real Ollama + LM Studio)
======================================================================

W1 Returning person (fl-welcomed true, last tab Chat), cold reload:
   - count requests to 127.0.0.1 in DevTools for 9s: about 16 (was 38). One full Bridge scan.
   - exactly ONE voice: the toast naming the real doors; no Chat suggest banner at the same time.
   - at 390px the toast is one tidy card (max 92vw), not a tall column.
   - tap the toast -> Connect, both cards: "Ollama is ready, pick a mind",
     "LM Studio is ready, pick a mind".
W1b Returning person who chose the SECOND model (qwen2.5) in Connect, cold reload:
   - fl_ollamaModel is still qwen2.5, fl_modelSource still user; send a chat: the POST model is qwen2.5.
W2 New person, cold freelattice.com/#connect:
   - lands on Connect once; no toast; no welcome overlay on top; about 12 requests (was 73).
   - tap another tab during boot: it stays there (no yank back to Connect or Garden).
W3 Tap an LM Studio model in Connect:
   - fl_provider = lmstudio, fl_lmstudioHost = 127.0.0.1:1234, fl_ollamaHost unchanged (null),
     the tapped button has aria-pressed="true" and .is-chosen, status reads "Local · <model>",
     chat sends to 127.0.0.1:1234/v1/chat/completions.
   - tap an Ollama model afterwards: the mark moves to the Ollama card.
W4 Chat Box field (Chat > the Box row):
   - default shows "This computer only", the switch off, the line hidden.
   - type 8.8.8.8 -> "That address is on the public internet, so it is blocked. Nothing was sent."
     and ZERO network requests.
   - type 192.168.1.50 with the switch off -> the home-network sentence, ZERO requests.
   - turn the switch on -> Kirk's line appears; type 192.168.1.50 once -> it is asked and saved
     (fl_homeNet_addr, fl_ollamaHost 192.168.1.50:11434); reload -> still on, still there.
   - turn the switch off (one tap) -> back to 127.0.0.1:11434; turn it on -> the address returns
     without retyping.
   - 8.8.8.8 with the switch ON is still refused.
W5 Settings > Ollama Address: 192.168.1.50:11434 refused while off (v0.4 words + switch
   sentence); saved while on; 203.0.113.5 refused either way.
W6 theLatticeTree: Settings > Connect lists LM Studio as its own card; Garden "Find local minds"
   right after Connect opens does not ask LM Studio a second time (DevTools); tapping an LM Studio
   model in Connect marks it and Find's list agrees ("LM Studio").

Screenshots from the prototype walk (for reference, not committed): W1 main vs boot at 390,
W1 toast 390/1280, W1b toast tapped -> Connect, W2 main vs boot #connect, W3 LM Studio tapped,
W4 switch off refused / on (390 and 1280).

======================================================================
12. PRS
======================================================================

FreeLattice PR title:
  layer: one cold-start look, LM Studio in Connect, home-network switch (v-connect-boot-probe-v0)
Body (short):
  F2 FlConnect.bootProbe (one in-flight look, 5s cache) + resolveOllamaBase join.
  F1 window.FlColdStart owns cold start: one look, land once, one voice. Old timers step aside.
  F3 LM Studio is a first-class door in Connect; no false "Yes, use Ollama"; LM Studio status words.
  F4 Kirk's switch: This computer only by default; "Also use a computer on my home network";
     private IPv4 only (10/8, 172.16/12, 192.168/16); 169.254, names, public always refused;
     remembered; one tap reverts; future hook for signed remote minds. Settings address follows
     the same switch.
  Cold boot 38 -> 16 local requests; #connect 73 -> 12. Smoke: SMOKE_OK connect boot probe v0.1.
  Locks: root sw.js == main, docs/sw.js untouched, index == app, fl-connect.js == Alpha, no
  markers, inline scripts parse, textContent, no bare *, no confirm().
  Depends on: env heal v0.1.1 (merged first) and Tree any-model v0.1.

theLatticeTree PR title:
  layer: one shared look for Connect and Find, LM Studio door in Connect (v-connect-boot-probe-v0)
Body (short):
  Shared fl-connect.js byte-identical with FreeLattice. local-mind-probe look() joins
  FlConnect.bootProbe and reuses its LM Studio / direct Ollama answer. LM Studio taps merge as the
  LM Studio door (/v1/models). Test: SMOKE_OK connect boot probe alpha v0.1. Depends on Tree
  any-model v0.1.

Never push to main directly. Never force-push. Merge FreeLattice first, then Alpha, and re-run the
cmp on both mains after merge.

======================================================================
13. HELD BACK / NEXT (not in this paste)
======================================================================

  - BrowseModels.init (app.html ~55045) still makes its own /api/tags call at boot. Next: read
    FlConnect.peekBootProbe() first. (It is the second tags call in W1.)
  - The 8s Connect loop still uses a fresh probe every tick when the panel is open; fine, it is
    the person's own look.
  - Home network for Alpha: not added (C7).
  - Signed remote minds: hook only (C10). No code path, no widened ranges.
  - Connect's own port field still accepts only a port on 127.0.0.1 (v0.4 rule unchanged).


======================================================================
APPENDIX A: FreeLattice docs/app.html, full exact diff vs main 7db2d94 (git apply --check clean on 7db2d94; index.html gets the same bytes by copy)
======================================================================

diff --git a/docs/app.html b/docs/app.html
index fa946fa..b4fd82e 100644
--- a/docs/app.html
+++ b/docs/app.html
@@ -2037,6 +2037,13 @@ header .mantra {
 #flBoxPointerStatus[data-fl-box-status="ready"] { color: #34d399; }
 #flBoxPointerStatus[data-fl-box-status="cors-blocked"] { color: #fbbf24; cursor: pointer; }
 #flBoxPointerStatus[data-fl-box-status="unreachable"] { color: #f87171; }
+/* v-connect-boot-probe-v0 (F4): home-network switch under the Box field */
+.fl-homenet { flex: 1 1 100%; display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px; font-size: 0.72rem; color: var(--text-muted); font-family: Georgia, serif; }
+.fl-homenet-mode { color: #a5f3fc; }
+.fl-homenet-switch { display: inline-flex; align-items: center; gap: 6px; min-height: 32px; cursor: pointer; }
+.fl-homenet-switch input { width: 18px; height: 18px; accent-color: #34d399; }
+.fl-homenet-line, .fl-homenet-refusal { flex: 1 1 100%; margin: 0; line-height: 1.45; }
+.fl-homenet-refusal { color: var(--gold, #e8b019); }
 .chat-overflow-btn {
   background: transparent;
   border: none;
@@ -11749,6 +11756,8 @@ input:checked + .sf-slider:before { transform: translateX(16px); }
   transform: translateX(-50%) translateY(0);
 }
 .fl-ollama-toast .fl-toast-icon { font-size: 1.2rem; }
+/* v-connect-boot-probe-v0: honest door names need room on a phone */
+.fl-ollama-toast { width: max-content; max-width: min(92vw, 440px); box-sizing: border-box; }
 .fl-ollama-toast .fl-toast-close {
   margin-left: 12px;
   background: none;
@@ -16507,6 +16516,13 @@ textarea.ag-input { min-height: 60px; resize: vertical; font-family: inherit; }
               <input id="flBoxPointerInput" type="text" placeholder="localhost:11434 or 192.168.1.50" autocomplete="off" spellcheck="false" aria-label="Local or LAN AI address">
               <span id="flBoxPointerStatus" data-fl-box-status="idle" title=""></span>
             </div>
+            <!-- v-connect-boot-probe-v0 (F4): this computer only, unless the person turns on home network -->
+            <div class="fl-homenet v-connect-boot-probe-v0" id="flHomeNet" data-fl-homenet="off">
+              <span class="fl-homenet-mode" id="flHomeNetMode">This computer only</span>
+              <label class="fl-homenet-switch" for="flHomeNetSwitch"><input type="checkbox" id="flHomeNetSwitch" role="switch" aria-checked="false" aria-describedby="flHomeNetLine"> Also use a computer on my home network</label>
+              <p class="fl-homenet-line" id="flHomeNetLine" hidden>Only addresses inside your home network are allowed (like 192.168.x.x). Public internet addresses are always blocked.</p>
+              <p class="fl-homenet-refusal" id="flHomeNetRefusal" role="status" hidden></p>
+            </div>
             <button class="chat-overflow-btn" id="chatOverflowBtn" onclick="FLChatOverflow.toggle()" aria-label="More options" title="More options">&#8943;</button>
             <div class="chat-overflow-popup" id="chatOverflowPopup">
               <button class="mic-btn" id="micBtn" onclick="toggleVoiceInput();FLChatOverflow.close()" title="Voice input" style="display:none;">
@@ -25898,7 +25914,7 @@ async function switchToConversation(convId) {
       // This prevents "Configure your model" from showing to someone who just connected Ollama.
       var _connected = state.isLocal || (state.apiKey && state.apiKey.length > 0);
       if (_connected) {
-        var _statusDesc = state.isLocal ? ('Ollama (' + (state.ollamaModel || 'llama3.2') + ')') : (PROVIDERS[state.provider] ? PROVIDERS[state.provider].name : state.provider);
+        var _statusDesc = state.isLocal ? ((state.provider === 'lmstudio' ? 'LM Studio (' : 'Ollama (') + (state.ollamaModel || 'llama3.2') + ')') : (PROVIDERS[state.provider] ? PROVIDERS[state.provider].name : state.provider); // v-connect-boot-probe-v0 (F3)
         // v5.79.44-chat-our-way: Chat empty state stays Chat (no Sophia identity bleed).
         div.textContent = 'You\'re safe here. Connected to ' + _statusDesc + '. Say whatever you need.';
       } else {
@@ -26943,6 +26959,8 @@ function switchTab(tabId) {
   }
   // Remember last tab for returning visits
   try { localStorage.setItem('fl-last-tab', tabId); } catch(e) {}
+  // v-connect-boot-probe-v0 (F1): a tab the person picked while booting wins over late landing
+  try { if (window.FlColdStart && FlColdStart.noteTab) FlColdStart.noteTab(tabId); } catch (eCs) {}
   LatticeEvents.emit('tabChanged', { tabId: tabId });
   LatticeEvents.emit('tabActivated:' + tabId, { tabId: tabId });
   // Also emit legacy events for community sub-tabs
@@ -27767,6 +27785,8 @@ const FlOnboarding = (function() {
 
   // Main app load detection — show toast if no provider configured
   function detectOnLoad() {
+    // v-connect-boot-probe-v0 (F1): the cold-start owner speaks at boot; this stays for pages without it
+    try { if (window.FlColdStart && FlColdStart.owns()) return; } catch (eOwn) {}
     var hasProvider = false;
     try {
       hasProvider = (typeof flHasOneMindConnected === 'function' && flHasOneMindConnected());
@@ -27811,11 +27831,56 @@ const FlOnboarding = (function() {
     // Don't show during welcome experience
     var welcomeOverlay = document.getElementById('welcomeOverlay');
     if (welcomeOverlay && !welcomeOverlay.classList.contains('welcome-gone')) return;
+    // v-connect-boot-probe-v0 (F1): the old id above is gone; the Grandmother Door is flWelcomeOverlay.
+    // Also never ask on top of Connect, which already lists the minds.
+    if (flToastWouldRace()) return;
     setTimeout(function() {
+      if (flToastWouldRace()) return;
       toast.classList.add('fl-toast-show');
     }, 2000);
   }
 
+  // v-connect-boot-probe-v0 (F1): true when the toast would talk over another voice
+  function flToastWouldRace() {
+    try {
+      var door = document.getElementById('flWelcomeOverlay');
+      if (door && door.classList.contains('fl-welcome-visible')) return true;
+      var tabC = document.getElementById('tab-connect');
+      if (tabC && tabC.classList.contains('active')) return true;
+      if (window.FlConnect && typeof FlConnect.hasLocalMind === 'function' && FlConnect.hasLocalMind()) return true;
+    } catch (e) {}
+    return false;
+  }
+
+  // v-connect-boot-probe-v0 (F1/F3): the cold-start owner hands over the one shared look.
+  // Names the door that really answered (Ollama, Bridge, LM Studio). textContent only.
+  function offerFromReport(report) {
+    try { if (localStorage.getItem('fl_ollama_toast_dismissed')) return false; } catch (eD) {}
+    var doors = (report && report.doors) || [];
+    var names = [];
+    var all = [];
+    doors.forEach(function (d) {
+      if (d && d.models && d.models.length) {
+        names.push(d.name);
+        d.models.forEach(function (n) { all.push({ name: String(n), _door: d.id }); });
+      }
+    });
+    if (!all.length) return false;
+    detectedModels = all;
+    var txt = document.getElementById('flOllamaToastText');
+    if (txt) {
+      while (txt.firstChild) txt.removeChild(txt.firstChild);
+      var who = names.length > 1 ? names.slice(0, -1).join(', ') + ' and ' + names[names.length - 1] : names[0];
+      txt.appendChild(document.createTextNode(who + (names.length > 1 ? ' are' : ' is') + ' ready on this computer. '));
+      var strong = document.createElement('strong');
+      strong.style.color = 'var(--accent)';
+      strong.textContent = 'Tap to choose a mind.';
+      txt.appendChild(strong);
+    }
+    showOllamaToast();
+    return true;
+  }
+
   function acceptToast() {
     var toast = document.getElementById('flOllamaToast');
     if (toast) toast.classList.remove('fl-toast-show');
@@ -27854,6 +27919,7 @@ const FlOnboarding = (function() {
   }
 
   return {
+    offerFromReport: offerFromReport, // v-connect-boot-probe-v0
     init: init,
     detectForWelcome: detectForWelcome,
     recheckOllama: recheckOllama,
@@ -31504,6 +31570,7 @@ function flUseBridge() {
   } catch (e0) {}
   try {
     if (typeof _ollamaResolvedBase !== 'undefined') _ollamaResolvedBase = null;
+    if (typeof flForgetLocalLook === 'function') flForgetLocalLook(); // v-connect-boot-probe-v0
   } catch (e1) {}
   try {
     if (typeof state !== 'undefined' && state) {
@@ -31522,6 +31589,8 @@ function flUseBridge() {
 
 (function flBridgeDetectBoot() {
   function run() {
+    // v-connect-boot-probe-v0 (F1): the cold-start owner paints the chip from the one shared look
+    try { if (window.FlColdStart && FlColdStart.owns()) return; } catch (eOwn) {}
     try { flBridgeDetect(); } catch (e) {}
   }
   if (typeof document !== 'undefined') {
@@ -31604,6 +31673,7 @@ function settingsSetMode(mode) {
     var statusDot = document.getElementById('settingsStatusDot');
     if (statusText) statusText.textContent = 'Connect card — allow the door, then look again…';
     var lmHost = localStorage.getItem('fl_lmstudioHost') || 'localhost:1234';
+    if (typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(lmHost)) lmHost = '127.0.0.1:1234'; // v-connect-boot-probe-v0 (F4)
     var ollamaProbe = fetch(getOllamaBaseUrl() + '/api/tags', { method: 'GET' })
       .then(function(r) { return r.ok ? r.json() : Promise.reject(); })
       .then(function(d) { return { provider: 'ollama', data: d }; });
@@ -32153,8 +32223,12 @@ function flMaybeShowWelcome() {
   var overlay = document.getElementById('flWelcomeOverlay');
   if (!overlay) return;
 
+  // v-connect-boot-probe-v0 (F1): a deep link to Connect is the door they asked for
+  try { if (window.FlColdStart && FlColdStart.landed() === 'connect') return; } catch (eW0) {}
   // 1-second delay so the app renders first, then fade in
   setTimeout(function() {
+    // v-connect-boot-probe-v0 (F1): never land late on top of someone already tapping; it waits for next visit
+    try { if (window.FlColdStart && FlColdStart.userMoved()) return; } catch (eW1) {}
     overlay.style.display = 'flex';
     // Force reflow before adding the visible class so the transition fires
     void overlay.offsetWidth;
@@ -33671,6 +33745,21 @@ document.addEventListener('DOMContentLoaded', async () => {
     var lastTab = localStorage.getItem('fl-last-tab');
     // Legacy tab mapping
     if (lastTab === 'channels' || lastTab === 'mesh') lastTab = 'community';
+    // v-connect-boot-probe-v0 (F1): land once. A deep link to Connect wins; a tab the
+    // person already picked while the page was waking wins; then last tab / Garden.
+    try {
+      if (window.FlColdStart) {
+        var _flPicked = FlColdStart.userTab ? FlColdStart.userTab() : '';
+        var _flDeep = FlColdStart.deepLink ? FlColdStart.deepLink() : '';
+        if (_flPicked || _flDeep === 'connect') {
+          if (isFirstVisit) localStorage.setItem('fl-has-visited', '1');
+          if (_flPicked) { FlColdStart.markLanded(_flPicked); return; }
+          FlColdStart.markLanded('connect');
+          switchTab('connect');
+          return;
+        }
+      }
+    } catch (eLand) {}
 
     if (isFirstVisit) {
       // First visit: Garden is home. Mark visited.
@@ -33700,6 +33789,7 @@ document.addEventListener('DOMContentLoaded', async () => {
       // Returning, no specific tab or was on Garden: stay on Garden
       switchTab('garden');
     }
+    try { if (window.FlColdStart) FlColdStart.markLanded(isFirstVisit ? 'garden' : (lastTab || 'garden')); } catch (eLand2) {}
   })();
 
   // Update memory button label with count
@@ -33779,14 +33869,18 @@ document.addEventListener('DOMContentLoaded', async () => {
 
   // Initialize Ollama auto-detection (v4.4)
   // Don't fire during onboarding — let the user choose first
-  if (safeGet('fl-welcomed') === 'true' || safeGet('fl_onboardingComplete') === 'true') {
+  // v-connect-boot-probe-v0 (F1): with the cold-start owner here, it decides who speaks
+  var _flOwnerBoot = false;
+  try { _flOwnerBoot = !!(window.FlColdStart && FlColdStart.owns()); } catch (eOwnB) {}
+  if (!_flOwnerBoot && (safeGet('fl-welcomed') === 'true' || safeGet('fl_onboardingComplete') === 'true')) {
     owAutoDetect();
   }
+  try { if (window.FlColdStart && FlColdStart.appBooted) FlColdStart.appBooted(); } catch (eBoot) {}
   // v5.78: Zero-Click Connect — auto-connect if no saved connection
   (function() {
     var hasKey = localStorage.getItem('fl_apiKey_enc') || localStorage.getItem('fl_apiKey');
     var isLocal = localStorage.getItem('fl_isLocal') === 'true';
-    if (!hasKey && !isLocal) {
+    if (!hasKey && !isLocal && !_flOwnerBoot) {
       setTimeout(flAutoConnect, 1500); // slight delay so page renders first
     }
   })();
@@ -34156,6 +34250,7 @@ async function probeLocalMind() {
   try {
     var lmHost = localStorage.getItem('fl_lmstudioHost') || 'localhost:1234';
     var lmBase = (lmHost.indexOf('://') === -1 ? 'http://' + lmHost : lmHost).replace(/\/+$/, '');
+    if (typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(lmBase)) lmBase = 'http://127.0.0.1:1234'; // v-connect-boot-probe-v0 (F4)
     var lr = await fetch(lmBase + '/v1/models', { method: 'GET', mode: 'cors', signal: flAbortTimeout(3000) });
     if (lr.ok) {
       var ld = await lr.json();
@@ -34329,6 +34424,64 @@ function getOllamaBaseUrl() {
   return FL_OLLAMA_LOCAL;
 }
 
+// v-connect-boot-probe-v0 (F2): everyone who asks at the same moment shares one look.
+// A finished look is reused for FL_LOCAL_LOOK_TTL_MS, unless something changed the
+// answer since (flForgetLocalLook, or any new value written to _ollamaResolvedBase).
+// The original resolveOllamaBase body above is untouched; it is wrapped here.
+var FL_LOCAL_LOOK_TTL_MS = 5000;
+var _flResolveNow = resolveOllamaBase;
+var _flResolveInflight = null;
+var _flResolveAt = 0;
+var _flResolveValue = null;
+function flForgetLocalLook() {
+  _flResolveInflight = null;
+  _flResolveAt = 0;
+  _flResolveValue = null;
+  try { if (window.FlConnect && typeof FlConnect.forgetBootProbe === 'function') FlConnect.forgetBootProbe(); } catch (e) {}
+}
+function flSeedLocalLook(value) {
+  _ollamaResolvedBase = value || null;
+  _flResolveValue = _ollamaResolvedBase;
+  _flResolveAt = Date.now();
+}
+resolveOllamaBase = function (forceRefresh) {
+  if (_flResolveInflight) return _flResolveInflight;
+  if (_flResolveAt && (Date.now() - _flResolveAt) < FL_LOCAL_LOOK_TTL_MS && _ollamaResolvedBase === _flResolveValue) {
+    return Promise.resolve(_flResolveValue);
+  }
+  var p = Promise.resolve(_flResolveNow(forceRefresh)).then(function (v) {
+    _flResolveAt = Date.now();
+    _flResolveValue = v;
+    if (_flResolveInflight === p) _flResolveInflight = null;
+    return v;
+  }, function (err) {
+    if (_flResolveInflight === p) _flResolveInflight = null;
+    throw err;
+  });
+  _flResolveInflight = p;
+  return p;
+};
+window.flForgetLocalLook = flForgetLocalLook;
+window.flSeedLocalLook = flSeedLocalLook;
+
+// v-connect-boot-probe-v0 (F4): a saved address is used only when it is this
+// computer, or a home-network address while the switch is on. Public: never.
+// Wrapped (the original getOllamaBaseUrl body above is untouched).
+var _flBaseUrlNow = getOllamaBaseUrl;
+getOllamaBaseUrl = function () {
+  var b = _flBaseUrlNow();
+  try {
+    if (typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(b)) {
+      var man = flManualBaseFirst();
+      if (man) return man;
+      var bp = parseInt(localStorage.getItem('fl_bridgePort') || '', 10);
+      if (bp && bp !== 11434) return 'http://127.0.0.1:' + bp;
+      return FL_OLLAMA_LOCAL;
+    }
+  } catch (eG) {}
+  return b;
+};
+
 // v5.43.6 (Opus): the /ollama same-origin proxy probe makes sense for
 // self-hosted FreeLattice (localhost / 127.0.0.1 / 192.168.* / file://)
 // where the user runs a reverse-proxy in front. On the public mirrors
@@ -34412,9 +34565,19 @@ function flSaveOllamaHost() {
   var refusal = document.getElementById('ollamaHostRefusal');
   if (refusal) { refusal.hidden = true; refusal.textContent = ''; }
   if (val && val !== 'localhost:11434' && val !== '127.0.0.1:11434') {
-    if (!flWriteOllamaHostLoopbackOnly(val)) {
+    // v-connect-boot-probe-v0 (F4): same one switch as Chat. On: a home-network address is fine.
+    var _flHomeOk = false;
+    try {
+      if (typeof FLHomeNet !== 'undefined' && FLHomeNet && FLHomeNet.zoneOf(val) === 'home' && FLHomeNet.isOn()) {
+        _flHomeOk = FLHomeNet.useAddress(val, 'ollama');
+      }
+    } catch (eHn) {}
+    if (!_flHomeOk && !flWriteOllamaHostLoopbackOnly(val)) {
       if (refusal) {
         refusal.textContent = 'For safety this only talks to your own computer. Try 127.0.0.1 with your port, like 127.0.0.1:11500.';
+        try {
+          if (typeof FLHomeNet !== 'undefined' && FLHomeNet) refusal.textContent += ' ' + FLHomeNet.refusalFor(val);
+        } catch (eHr) {}
         refusal.hidden = false;
       }
       return;
@@ -34424,11 +34587,185 @@ function flSaveOllamaHost() {
   }
   // Invalidate the resolved base so next fetch uses the new host
   _ollamaResolvedBase = null;
+  try { if (typeof flForgetLocalLook === 'function') flForgetLocalLook(); } catch (eF) {} // v-connect-boot-probe-v0
   updateOllamaProviderUrl();
   try { if (typeof FLBoxPointer !== 'undefined' && FLBoxPointer.syncFromStorage) FLBoxPointer.syncFromStorage(); } catch (e) {}
 }
 window.flSaveOllamaHost = flSaveOllamaHost;
 
+// ═══════════════════════════════════════════════════════════════
+// FLHomeNet: v-connect-boot-probe-v0 (F4). Kirk's decision, 2026-09-28.
+// Default: "This computer only". One clear switch: "Also use a computer on
+// my home network". While it is on, a home address typed once is used and
+// remembered until changed. One tap turns it off and everything goes back
+// to this computer. Public internet addresses are always refused.
+// Home ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 (IPv4 numbers only).
+// Refused on purpose: 169.254.0.0/16 (link-local: no home router hands it out,
+// and 169.254.169.254 is the cloud metadata door), 100.64.0.0/10 (carrier NAT,
+// not your home), 0.0.0.0/8, multicast and above, names other than localhost
+// (a name can point anywhere), IPv6 other than ::1.
+// Future hook: signed remote minds get their own signed door beside this one.
+// They never widen these ranges. No confirm dialogs. No approval screens.
+// ═══════════════════════════════════════════════════════════════
+var FL_HOMENET_KEY = 'fl_homeNet';
+var FL_HOMENET_ADDR_KEY = 'fl_homeNet_addr';
+var FL_HOMENET_LINE = 'Only addresses inside your home network are allowed (like 192.168.x.x). Public internet addresses are always blocked.';
+window.FLHomeNet = (function () {
+  function hostnameOf(raw) {
+    var s = String(raw || '').trim();
+    if (!s) return '';
+    if (s.indexOf('://') === -1) s = 'http://' + s;
+    try { return String(new URL(s).hostname || '').toLowerCase(); } catch (e) { return ''; }
+  }
+  // 'this' | 'home' | 'public' | 'name' | 'invalid'
+  function zoneOf(raw) {
+    var h = hostnameOf(raw);
+    if (!h) return 'invalid';
+    if (h === 'localhost' || h === '[::1]' || h === '::1') return 'this';
+    var m = h.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
+    if (!m) return (h.indexOf(':') !== -1 || h.charAt(0) === '[') ? 'public' : 'name';
+    var a = +m[1], b = +m[2];
+    if (a > 255 || b > 255 || +m[3] > 255 || +m[4] > 255) return 'invalid';
+    if (a === 127) return 'this';
+    if (a === 10) return 'home';
+    if (a === 172 && b >= 16 && b <= 31) return 'home';
+    if (a === 192 && b === 168) return 'home';
+    // Future hook (signed remote minds): a separate signed path, never here.
+    return 'public';
+  }
+  function isOn() {
+    try { return localStorage.getItem(FL_HOMENET_KEY) === 'on'; } catch (e) { return false; }
+  }
+  function allows(raw) {
+    if (/^\/(?!\/)/.test(String(raw || ''))) return true; // same-origin /ollama proxy route
+    var z = zoneOf(raw);
+    return z === 'this' || (z === 'home' && isOn());
+  }
+  function address() {
+    try { return localStorage.getItem(FL_HOMENET_ADDR_KEY) || ''; } catch (e) { return ''; }
+  }
+  function refusalFor(raw) {
+    var z = zoneOf(raw);
+    if (z === 'public') return 'That address is on the public internet, so it is blocked. Nothing was sent.';
+    if (z === 'name') return 'Type the number address, like 192.168.1.50. A name can point anywhere, so names are not used. Nothing was sent.';
+    if (z === 'home' && !isOn()) return 'That is a computer on your home network. Turn on "Also use a computer on my home network" beside the Box field in Chat to use it. Nothing was sent.';
+    if (z === 'invalid') return 'That does not look like an address. Nothing was sent.';
+    return '';
+  }
+  function forget() {
+    try { _ollamaResolvedBase = null; } catch (e0) {}
+    try { if (typeof flForgetLocalLook === 'function') flForgetLocalLook(); } catch (e1) {}
+    try { if (typeof updateOllamaProviderUrl === 'function') updateOllamaProviderUrl(); } catch (e2) {}
+    try {
+      if (typeof PROVIDERS !== 'undefined' && PROVIDERS.lmstudio && typeof getLMStudioBase === 'function') {
+        PROVIDERS.lmstudio.url = getLMStudioBase() + '/v1/chat/completions';
+      }
+    } catch (e3) {}
+  }
+  // Remember a home address typed once, into the store its kind already uses.
+  function useAddress(raw, kind) {
+    if (!(zoneOf(raw) === 'home' && isOn())) return false;
+    var s = String(raw || '').trim();
+    if (s.indexOf('://') === -1) s = 'http://' + s;
+    var u;
+    try { u = new URL(s); } catch (e) { return false; }
+    var k = kind || ((u.port === '1234') ? 'lmstudio' : 'ollama');
+    var host = u.hostname + ':' + (u.port || (k === 'lmstudio' ? '1234' : '11434'));
+    try {
+      localStorage.setItem(FL_HOMENET_ADDR_KEY, host);
+      if (k === 'lmstudio') localStorage.setItem('fl_lmstudioHost', host);
+      else localStorage.setItem('fl_ollamaHost', host);
+    } catch (e2) {}
+    forget();
+    return true;
+  }
+  function paint() {
+    var on = isOn();
+    var wrap = document.getElementById('flHomeNet');
+    var sw = document.getElementById('flHomeNetSwitch');
+    var mode = document.getElementById('flHomeNetMode');
+    var line = document.getElementById('flHomeNetLine');
+    var input = document.getElementById('flBoxPointerInput');
+    if (wrap) wrap.setAttribute('data-fl-homenet', on ? 'on' : 'off');
+    if (sw) { sw.checked = on; sw.setAttribute('aria-checked', on ? 'true' : 'false'); }
+    if (mode) mode.textContent = on ? 'This computer and my home network' : 'This computer only';
+    if (line) { line.textContent = FL_HOMENET_LINE; line.hidden = !on; }
+    if (input) input.placeholder = on ? '192.168.1.50' : '127.0.0.1:11434';
+  }
+  function say(msg) {
+    var el = document.getElementById('flHomeNetRefusal');
+    if (!el) return;
+    el.textContent = msg || '';
+    el.hidden = !msg;
+  }
+  // One tap. On: the remembered home address comes back. Off: this computer only.
+  function setOn(on) {
+    try {
+      if (on) {
+        localStorage.setItem(FL_HOMENET_KEY, 'on');
+        var addr = address();
+        if (addr && zoneOf(addr) === 'home') {
+          if (/:1234$/.test(addr)) localStorage.setItem('fl_lmstudioHost', addr);
+          else localStorage.setItem('fl_ollamaHost', addr);
+        }
+      } else {
+        localStorage.removeItem(FL_HOMENET_KEY);
+        var oh = localStorage.getItem('fl_ollamaHost') || '';
+        if (oh && zoneOf(oh) !== 'this') localStorage.removeItem('fl_ollamaHost');
+        var lh = localStorage.getItem('fl_lmstudioHost') || '';
+        if (lh && zoneOf(lh) !== 'this') localStorage.removeItem('fl_lmstudioHost');
+      }
+    } catch (e) {}
+    forget();
+    say('');
+    paint();
+    try { if (typeof FLBoxPointer !== 'undefined' && FLBoxPointer.syncFromStorage) FLBoxPointer.syncFromStorage(); } catch (e2) {}
+    try {
+      var input = document.getElementById('flBoxPointerInput');
+      if (input) input.value = on ? (address() || '') : '';
+      if (typeof FLBoxPointer !== 'undefined' && FLBoxPointer.noteStatus) FLBoxPointer.noteStatus('idle', on ? '' : 'This computer');
+    } catch (e3) {}
+    return on;
+  }
+  // Addresses saved before this switch existed: a home address the person typed
+  // turns the switch on once (their earlier choice keeps working, and it shows);
+  // a public one is set aside (kept under fl_homeNet_refused, never used).
+  function migrate() {
+    try {
+      if (localStorage.getItem('fl_homeNet_seen')) return;
+      localStorage.setItem('fl_homeNet_seen', '1');
+      var keys = ['fl_ollamaHost', 'fl_localUrl', 'fl_lmstudioHost'];
+      for (var i = 0; i < keys.length; i++) {
+        var v = localStorage.getItem(keys[i]) || '';
+        if (!v) continue;
+        var z = zoneOf(v);
+        if (z === 'home') {
+          localStorage.setItem(FL_HOMENET_KEY, 'on');
+          if (!address()) localStorage.setItem(FL_HOMENET_ADDR_KEY, v.replace(/^https?:\/\//i, '').replace(/\/+$/, ''));
+        } else if (z === 'public' || z === 'name') {
+          localStorage.setItem('fl_homeNet_refused', v);
+          localStorage.removeItem(keys[i]);
+        }
+      }
+    } catch (e) {}
+  }
+  function init() {
+    migrate();
+    paint();
+    var sw = document.getElementById('flHomeNetSwitch');
+    if (sw && !sw.getAttribute('data-fl-homenet-bound')) {
+      sw.setAttribute('data-fl-homenet-bound', '1');
+      sw.addEventListener('change', function () { setOn(!!sw.checked); });
+    }
+  }
+  return {
+    zoneOf: zoneOf, isOn: isOn, allows: allows, address: address, useAddress: useAddress,
+    refusalFor: refusalFor, setOn: setOn, init: init, paint: paint, say: say, migrate: migrate,
+    LINE: FL_HOMENET_LINE
+  };
+})();
+window.flHomeNetAllowsBase = function (b) { return window.FLHomeNet ? FLHomeNet.allows(b) : true; };
+
 // ═══════════════════════════════════════════════════════════════
 // FLBoxPointer — v5.79.41-chat-box-pointer
 // Audit: Chat could not point at a local/LAN box. The host field
@@ -34494,6 +34831,13 @@ window.FLBoxPointer = (function() {
   }
 
   function persist(norm) {
+    // v-connect-boot-probe-v0 (F4): this computer always; home network only while the switch is on; public never
+    try {
+      if (norm && norm.host && typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(norm.host)) return false;
+      if (norm && norm.host && typeof FLHomeNet !== 'undefined' && FLHomeNet && FLHomeNet.zoneOf(norm.host) === 'home') {
+        localStorage.setItem(FL_HOMENET_ADDR_KEY, norm.host);
+      }
+    } catch (eHn) {}
     _host = norm.host || '';
     _kind = norm.kind || 'ollama';
     var hidden = document.getElementById('ollamaHostInput');
@@ -34504,6 +34848,7 @@ window.FLBoxPointer = (function() {
       try { localStorage.removeItem('fl_ollamaHost'); } catch (e2) {}
     }
     try { _ollamaResolvedBase = null; } catch (e3) {}
+    try { if (typeof flForgetLocalLook === 'function') flForgetLocalLook(); } catch (e3b) {} // v-connect-boot-probe-v0
     try { if (typeof updateOllamaProviderUrl === 'function') updateOllamaProviderUrl(); } catch (e4) {}
     if (_kind === 'openai-compat' || _kind === 'lmstudio') {
       try {
@@ -34571,6 +34916,17 @@ window.FLBoxPointer = (function() {
       paint('unreachable', 'Need a URL or IP');
       return { status: 'invalid', host: '', kind: 'invalid' };
     }
+    // v-connect-boot-probe-v0 (F4): refuse before any fetch; say it plainly under the field
+    try {
+      if (!norm.empty && typeof FLHomeNet !== 'undefined' && FLHomeNet) {
+        if (!FLHomeNet.allows(norm.host)) {
+          paint('unreachable', 'Not used');
+          FLHomeNet.say(FLHomeNet.refusalFor(norm.host));
+          return { status: 'refused', host: norm.host, kind: norm.kind, zone: FLHomeNet.zoneOf(norm.host) };
+        }
+        FLHomeNet.say('');
+      }
+    } catch (eHz) {}
     if (norm.empty && input && !String(input.value || '').trim()) {
       // Keep default host visible but don't force a probe on blank first paint.
       _host = 'localhost:11434';
@@ -34655,6 +35011,7 @@ window.FLBoxPointer = (function() {
   }
 
   function init() {
+    try { if (typeof FLHomeNet !== 'undefined' && FLHomeNet && FLHomeNet.init) FLHomeNet.init(); } catch (eHi) {} // v-connect-boot-probe-v0 (F4)
     syncFromStorage();
     var input = document.getElementById('flBoxPointerInput');
     var statusEl = document.getElementById('flBoxPointerStatus');
@@ -34700,6 +35057,15 @@ function flSaveLMStudioHost() {
   var input = document.getElementById('lmstudioHostInput');
   if (!input) return;
   var val = input.value.trim();
+  // v-connect-boot-probe-v0 (F4): this computer always; home network only while the switch is on; public never
+  try {
+    if (val && typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(val)) {
+      var lmRes = document.getElementById('lmstudioTestResult');
+      if (lmRes) lmRes.textContent = 'For safety this only talks to your own computer. ' + FLHomeNet.refusalFor(val);
+      return;
+    }
+    if (val && typeof FLHomeNet !== 'undefined' && FLHomeNet && FLHomeNet.zoneOf(val) === 'home') FLHomeNet.useAddress(val, 'lmstudio');
+  } catch (eLh) {}
   if (val && val !== 'localhost:1234') {
     safeSet('fl_lmstudioHost', val);
   } else {
@@ -34712,6 +35078,33 @@ function flSaveLMStudioHost() {
 }
 window.flSaveLMStudioHost = flSaveLMStudioHost;
 
+// v-connect-boot-probe-v0 (F4): LM Studio's saved host obeys the same one rule. Wrapped.
+var _flLMStudioBaseNow = getLMStudioBase;
+getLMStudioBase = function () {
+  var b = _flLMStudioBaseNow();
+  try { if (typeof FLHomeNet !== 'undefined' && FLHomeNet && !FLHomeNet.allows(b)) return 'http://127.0.0.1:1234'; } catch (e) {}
+  return b;
+};
+
+// v-connect-boot-probe-v0 (F3): Connect's LM Studio tap uses the same activation as the Test button
+function flUseLmStudioMind(name) {
+  try {
+    var base = getLMStudioBase();
+    if (typeof state !== 'undefined' && state) {
+      state.isLocal = true;
+      state.provider = 'lmstudio';
+      if (name) state.ollamaModel = name;
+    }
+    safeSet('fl_isLocal', 'true');
+    safeSet('fl_provider', 'lmstudio');
+    if (name) safeSet('fl_ollamaModel', name);
+    if (typeof PROVIDERS !== 'undefined' && PROVIDERS.lmstudio) PROVIDERS.lmstudio.url = base + '/v1/chat/completions';
+    if (name && typeof FLActiveModel !== 'undefined' && FLActiveModel.set) FLActiveModel.set(name, 'lmstudio', 'user');
+    if (typeof ModelSwitcher !== 'undefined' && ModelSwitcher.updateLabel) ModelSwitcher.updateLabel();
+  } catch (e) {}
+}
+window.flUseLmStudioMind = flUseLmStudioMind;
+
 function flTestLMStudio() {
   var result = document.getElementById('lmstudioTestResult');
   if (result) result.textContent = 'Testing...';
@@ -35208,7 +35601,12 @@ async function owAutoDetect() {
       var dismissed = localStorage.getItem('fl_ollama_suggest_dismissed');
       if (!dismissed) {
         var banner = document.getElementById('ollamaSuggestBanner');
-        if (banner) banner.classList.add('show');
+        if (banner) {
+          // v-connect-boot-probe-v0 (F3): Ollama really answered; keep the Ollama words
+          var yesO = banner.querySelector('.suggest-yes');
+          if (yesO) yesO.textContent = 'Yes, use Ollama';
+          banner.classList.add('show');
+        }
       }
     }
   } catch(e) {
@@ -35224,7 +35622,18 @@ async function owAutoDetect() {
         if (!dismissed) {
           var banner = document.getElementById('ollamaSuggestBanner');
           if (banner) {
-            banner.querySelector('.suggest-text').innerHTML = '\uD83D\uDC7B <strong>LM Studio detected!</strong> ' + lmsModels.length + ' model(s) available. Use it for completely private AI?';
+            // v-connect-boot-probe-v0 (F3): LM Studio answered, so the words and the button say LM Studio
+            var lmText = banner.querySelector('.suggest-text');
+            if (lmText) {
+              while (lmText.firstChild) lmText.removeChild(lmText.firstChild);
+              var lmStrong = document.createElement('strong');
+              lmStrong.textContent = 'LM Studio is ready on this computer.';
+              lmText.appendChild(document.createTextNode('\uD83C\uDF31 '));
+              lmText.appendChild(lmStrong);
+              lmText.appendChild(document.createTextNode(' ' + lmsModels.length + (lmsModels.length === 1 ? ' model' : ' models') + '. Choose one for completely private AI?'));
+            }
+            var yesL = banner.querySelector('.suggest-yes');
+            if (yesL) yesL.textContent = 'Yes, choose a mind';
             banner.classList.add('show');
           }
         }
@@ -35562,7 +35971,7 @@ function handleLocalToggle(init) {
     updateStatus();
     // Visual indicator + bridge for provider switch
     if (state.chatHistory.length > 0) {
-      var name = isLocal ? 'Ollama (' + (state.ollamaModel || 'llama3.2') + ')' : (PROVIDERS[state.provider] ? PROVIDERS[state.provider].name : state.provider);
+      var name = isLocal ? (state.provider === 'lmstudio' ? 'LM Studio (' : 'Ollama (') + (state.ollamaModel || 'llama3.2') + ')' : (PROVIDERS[state.provider] ? PROVIDERS[state.provider].name : state.provider); // v-connect-boot-probe-v0 (F3)
       addProviderSwitchDivider(name, '');
       state._providerJustSwitched = true;
     }
@@ -39678,6 +40087,8 @@ async function autoDiscoverAI() {
 // Run discovery on page load (after a short delay for DOM to settle)
 document.addEventListener('DOMContentLoaded', function() {
   setTimeout(function() {
+    // v-connect-boot-probe-v0 (F1): the cold-start owner already looked (and saves discovery memory)
+    try { if (window.FlColdStart && FlColdStart.owns()) return; } catch (eOwn) {}
     // Only scan if not already connected
     if (typeof state !== 'undefined' && !state.apiKey && !state.isLocal) {
       autoDiscoverAI();
@@ -52217,7 +52628,7 @@ if ('serviceWorker' in navigator) {
 <!-- Ollama Auto-Detection Toast (v4.9) -->
 <div class="fl-ollama-toast" id="flOllamaToast" onclick="FlOnboarding.acceptToast()">
   <span class="fl-toast-icon">&#127793;</span>
-  <span>Local AI detected! <strong style="color:var(--accent);">Click here to connect.</strong></span>
+  <span id="flOllamaToastText">Local AI detected! <strong style="color:var(--accent);">Click here to connect.</strong></span>
   <button class="fl-toast-close" onclick="event.stopPropagation(); FlOnboarding.dismissToast();">&times;</button>
 </div>
 
@@ -68589,6 +69000,8 @@ document.addEventListener('click', function(e) {
     try {
       var fromHash = !!(ev && ev.type === 'hashchange');
       if (_flcUserMoved && !fromHash) return;
+      // v-connect-boot-probe-v0 (F1): the cold-start owner already landed Connect once
+      try { if (!fromHash && window.FlColdStart && FlColdStart.landed() === 'connect') return; } catch (eL0) {}
       var hash = (location.hash || '').replace(/^#/, '');
       var q = '';
       try { q = new URLSearchParams(location.search || '').get('tab') || ''; } catch (e0) {}
@@ -68611,6 +69024,154 @@ document.addEventListener('click', function(e) {
   window.addEventListener('hashchange', goConnect);
 })();
 </script>
+<script>
+// v-connect-boot-probe-v0 (F1): ONE cold-start owner.
+// Starts the one shared look (FlConnect.bootProbe) as soon as the page is parsed,
+// decides where the person lands once, then lets exactly one voice speak about
+// local minds. The older boot timers (toast 4s, owAutoDetect, Bridge chip 1.2s,
+// discovery 3s, Zero-Click 1.5s, #connect retries) stay in the code and step
+// aside while the owner is here. Without FlConnect.bootProbe nothing changes.
+// No confirm dialogs. No approval screens. Nothing connects by itself.
+window.FlColdStart = (function () {
+  var SPEAK_AFTER_MS = 4000; // same breath the old toast took
+  var _started = false;
+  var _t0 = 0;
+  var _landed = '';
+  var _userMoved = false;
+  var _userTab = '';
+  var _booted = false;
+  var _bootWaiters = [];
+  var _spoken = false;
+  var _look = null;
+
+  function owns() {
+    return !!(window.FlConnect && typeof FlConnect.bootProbe === 'function');
+  }
+  function deepLink() {
+    try {
+      var hash = (location.hash || '').replace(/^#/, '');
+      var q = '';
+      try { q = new URLSearchParams(location.search || '').get('tab') || ''; } catch (e0) {}
+      if (hash === 'connect' || q === 'connect') return 'connect';
+    } catch (e) {}
+    return '';
+  }
+  function moved() { _userMoved = true; }
+  try {
+    document.addEventListener('pointerdown', moved, true);
+    document.addEventListener('keydown', moved, true);
+  } catch (eL) {}
+
+  function noteTab(tabId) { if (_userMoved && !_landed) _userTab = String(tabId || ''); }
+  function markLanded(tabId) { if (!_landed) _landed = String(tabId || 'garden'); }
+  function appBooted() {
+    if (_booted) return;
+    _booted = true;
+    var w = _bootWaiters; _bootWaiters = [];
+    w.forEach(function (fn) { try { fn(); } catch (e) {} });
+  }
+  function whenBooted() {
+    return new Promise(function (resolve) { if (_booted) resolve(); else _bootWaiters.push(resolve); });
+  }
+  function waitUntil(ms) {
+    var left = ms - (Date.now() - _t0);
+    return new Promise(function (resolve) { if (left <= 0) resolve(); else setTimeout(resolve, left); });
+  }
+
+  // Feed resolveOllamaBase's short shared cache, only when the look covered the same door
+  function seed(report) {
+    try {
+      if (typeof flSeedLocalLook !== 'function') return;
+      var want = (typeof getOllamaBaseUrl === 'function') ? String(getOllamaBaseUrl()).replace(/\/+$/, '') : '';
+      var via = (report && report.ollama && report.ollama.via) || '';
+      if (report && report.base && report.models && report.models.length &&
+          (via !== 'direct' || report.base === want)) {
+        flSeedLocalLook({ base: report.base, isProxy: false, viaBridge: /bridge/.test(via), viaBootProbe: true });
+      }
+    } catch (e) {}
+  }
+
+  // The Bridge chip, from the same look (was flBridgeDetectBoot at 1.2s)
+  function paintBridge(report) {
+    try {
+      if (!report || !report.bridge) { if (typeof flBridgeShowChip === 'function') flBridgeShowChip(false); return; }
+      var port = report.port || (report.bridge && (report.bridge.port || report.bridge._probedPort));
+      if (port && port !== 11434) {
+        try { localStorage.setItem('fl_bridgePort', String(port)); } catch (eP) {}
+        try { if (typeof _flBridgeLivePort !== 'undefined') _flBridgeLivePort = port; } catch (eLp) {}
+      }
+      if (typeof flBridgeShowChip === 'function') flBridgeShowChip(!!report.helped, port);
+      var field = document.getElementById('fl-bridge-port-field');
+      if (field && port) field.value = port;
+    } catch (e) {}
+  }
+
+  // Discovery memory, from the same look (was autoDiscoverAI at 3s)
+  function remember(report) {
+    try {
+      var doors = (report && report.doors) || [];
+      if (!doors.length || typeof saveDiscoveryMemory !== 'function') return;
+      saveDiscoveryMemory(doors.map(function (d) {
+        return { name: d.name, type: d.id === 'lmstudio' ? 'openai-compat' : 'ollama', url: d.base, models: d.models, status: 'ready', corsOk: true };
+      }));
+    } catch (e) {}
+  }
+
+  function connectShowing() {
+    var t = document.getElementById('tab-connect');
+    return !!(t && t.classList.contains('active'));
+  }
+  function doorShowing() {
+    var d = document.getElementById('flWelcomeOverlay');
+    return !!(d && (d.classList.contains('fl-welcome-visible') || (d.style && d.style.display === 'flex')));
+  }
+
+  // Exactly one voice. Connect showing: Connect speaks. Welcome door: it speaks
+  // (owAutoDetect runs when it closes, as today). A mind already chosen: only the
+  // status badges refresh. Otherwise the Local AI toast, naming the real door.
+  function speak(report) {
+    if (_spoken) return;
+    _spoken = true;
+    try {
+      if (connectShowing() || doorShowing()) return;
+      var welcomed = false;
+      try { welcomed = localStorage.getItem('fl-welcomed') === 'true' || localStorage.getItem('fl_onboardingComplete') === 'true'; } catch (eW) {}
+      var chosen = false;
+      try { chosen = !!(window.FlConnect && FlConnect.hasLocalMind()); } catch (eC) {}
+      if (chosen) return;
+      try { if (typeof flHasOneMindConnected === 'function' && flHasOneMindConnected()) return; } catch (eM) {}
+      // Local mode without a model yet: owAutoDetect only paints the status badges here (no banner)
+      try { if (welcomed && localStorage.getItem('fl_isLocal') === 'true' && typeof owAutoDetect === 'function') owAutoDetect(); } catch (eB) {}
+      if (typeof FlOnboarding !== 'undefined' && FlOnboarding.offerFromReport) FlOnboarding.offerFromReport(report);
+    } catch (e) {}
+  }
+
+  function start() {
+    if (_started || !owns()) return;
+    _started = true;
+    _t0 = Date.now();
+    _look = FlConnect.bootProbe({ fullScan: true });
+    _look.then(function (report) { seed(report); paintBridge(report); remember(report); });
+    Promise.all([_look, whenBooted(), waitUntil(SPEAK_AFTER_MS)]).then(function (all) { speak(all[0]); });
+  }
+
+  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
+  else setTimeout(start, 0);
+
+  return {
+    owns: owns,
+    deepLink: deepLink,
+    landed: function () { return _landed; },
+    markLanded: markLanded,
+    userMoved: function () { return _userMoved; },
+    userTab: function () { return _userTab; },
+    noteTab: noteTab,
+    appBooted: appBooted,
+    look: function () { return _look; },
+    SPEAK_AFTER_MS: SPEAK_AFTER_MS
+  };
+})();
+</script>
 
 
 <!-- Tool Consent (v5.39.1) — sibling of DepthConsent for the opposite

======================================================================
APPENDIX B: shared docs/modules/fl-connect.js, full exact diff (base = fl-connect.js AFTER the Tree any-model paste; apply by text anchors if the real Tree merge differs by a line or two)
======================================================================

diff --git a/docs/modules/fl-connect.js b/docs/modules/fl-connect.js
index b8fcc7e..03ba72f 100644
--- a/docs/modules/fl-connect.js
+++ b/docs/modules/fl-connect.js
@@ -2,6 +2,7 @@
 // fl-connect.js — FlConnect shared core (FreeLattice + theLatticeTree)
 //
 // Marker: v-connect-under-more-v0 · heal v0.1 · v-connect-port-picker-v0 · heal v0.2 · v-connect-heal-v0.4
+// Marker: v-connect-boot-probe-v0 (one shared look at boot; LM Studio beside Ollama; 127.0.0.1 only)
 // Layer, never delete. Prefer helped Bridge (11435…) before bare 11434.
 // Port-only loopback: fl_localPort_manual holds "11500" only — never foreign hosts.
 // Soft leave sw.js. Alpha: fl_alpha_local_mind. Never bare *.
@@ -15,6 +16,10 @@
   var MANUAL_KEY = 'fl_localPort_manual';
   var INSTALL_BRIDGE = 'https://freelattice.com/install.html#bridge-download';
   var LOOP_MAX_MS = 5 * 60 * 1000;
+  // v-connect-boot-probe-v0 (F2): one look, shared. A look younger than this is reused.
+  var BOOT_TTL_MS = 5000;
+  // v-connect-boot-probe-v0 (F3): LM Studio's own server on this computer.
+  var LM_DEFAULT = 'http://127.0.0.1:1234';
 
   var _timer = null;
   var _hostEl = null;
@@ -28,6 +33,9 @@
   var _tickN = 0;
   var _fullScanMiss = false;
   var _manualRejectMsg = '';
+  var _bootInflight = null;
+  var _bootReport = null;
+  var _bootAt = 0;
 
   function isAlpha() {
     try {
@@ -68,6 +76,7 @@
 
   function setManualHost(raw) {
     _manualRejectMsg = '';
+    forgetLooks(); // v-connect-boot-probe-v0: a new port means a new look
     try {
       raw = String(raw || '').trim();
       if (!raw) {
@@ -92,6 +101,7 @@
     // v-connect-heal-v0.4 item 5: Use automatic also forgets the host that
     // remember() wrote for this same port, and the cached resolved base.
     var was = getManualPort();
+    forgetLooks(); // v-connect-boot-probe-v0
     try { localStorage.removeItem(MANUAL_KEY); } catch (e) {}
     try {
       if (was && localStorage.getItem('fl_ollamaHost') === '127.0.0.1:' + was) localStorage.removeItem('fl_ollamaHost');
@@ -271,6 +281,106 @@
     return report;
   }
 
+  // v-connect-boot-probe-v0 (F3): LM Studio is a mind on this computer too.
+  // 127.0.0.1 only. A saved fl_lmstudioHost is used only when it is this computer.
+  function lmStudioBase() {
+    try {
+      var saved = String(localStorage.getItem('fl_lmstudioHost') || '').trim();
+      var m = saved.match(/^(?:https?:\/\/)?(127\.0\.0\.1|localhost)(?::(\d{1,5}))?\/?$/i);
+      if (m) {
+        var p = m[2] ? parseInt(m[2], 10) : 1234;
+        if (p >= 1 && p <= 65535 && p !== 11434) return 'http://127.0.0.1:' + p;
+      }
+    } catch (e) {}
+    return LM_DEFAULT;
+  }
+
+  function lmModelCap() {
+    try {
+      var cap = root.LocalMindProbe && parseInt(root.LocalMindProbe.MODEL_CAP, 10);
+      if (cap && cap > 0) return cap;
+    } catch (e) {}
+    return 64;
+  }
+
+  async function lmStudioLook() {
+    var base = lmStudioBase();
+    var out = { base: base, models: [], ok: false, asked: true, quiet: false };
+    try {
+      var r = await fetch(base + '/v1/models', { method: 'GET', mode: 'cors', signal: abortMs(2500) });
+      if (!r.ok) return out;
+      out.ok = true;
+      var data = await r.json();
+      out.models = ((data && data.data) || []).map(function (m) {
+        return String((m && (m.id || m.name)) || '');
+      }).filter(Boolean).slice(0, lmModelCap());
+    } catch (e) { out.quiet = true; }
+    return out;
+  }
+
+  // v-connect-boot-probe-v0 (F2): ONE look at this computer for everyone who asks
+  // at boot (Connect, the Local AI toast, the Chat suggestion, the Bridge chip,
+  // discovery). One promise in flight; a finished look is reused for BOOT_TTL_MS.
+  // fresh:true (a person tapped Look again) skips the reuse but still joins a
+  // look that is already on its way. No timers: age is read from Date.now().
+  function bootProbe(opts) {
+    opts = opts || {};
+    if (_bootInflight) return _bootInflight;
+    if (!opts.fresh && _bootReport && (Date.now() - _bootAt) < BOOT_TTL_MS) {
+      return Promise.resolve(_bootReport);
+    }
+    var p = Promise.all([
+      probe({ fullScan: !!opts.fullScan, gesture: !!opts.gesture }).catch(function () { return null; }),
+      lmStudioLook().catch(function () { return null; })
+    ]).then(function (both) {
+      var report = both[0] || { models: [] };
+      report.lmstudio = both[1] || null;
+      report.lmstudioAsked = !!(both[1] && both[1].asked);
+      report.at = Date.now();
+      report.doors = doorsFrom(report);
+      _bootReport = report;
+      _bootAt = report.at;
+      if (_bootInflight === p) _bootInflight = null;
+      return report;
+    }, function () {
+      if (_bootInflight === p) _bootInflight = null;
+      return { models: [], doors: [], at: Date.now() };
+    });
+    _bootInflight = p;
+    return p;
+  }
+
+  function peekBootProbe() {
+    return (_bootReport && (Date.now() - _bootAt) < BOOT_TTL_MS) ? _bootReport : null;
+  }
+
+  function forgetBootProbe() {
+    _bootReport = null;
+    _bootAt = 0;
+    _bootInflight = null;
+  }
+
+  // FreeLattice also keeps a short shared look for resolveOllamaBase; forget both.
+  function forgetLooks() {
+    try { if (typeof root.flForgetLocalLook === 'function') { root.flForgetLocalLook(); return; } } catch (e) {}
+    forgetBootProbe();
+  }
+
+  // Plain list of who answered: [{ id, name, base, models }]. Names only (strings).
+  function doorsFrom(report) {
+    var doors = [];
+    report = report || {};
+    var names = (report.models || []).map(function (m) { return (m && (m.name || m.model)) || String(m); }).filter(Boolean);
+    if (report.base && names.length) {
+      var viaBridge = !!(report.helped && report.ollama && /bridge/.test(String(report.ollama.via || '')));
+      doors.push({ id: viaBridge ? 'bridge' : 'ollama', name: viaBridge ? 'Bridge' : 'Ollama', base: report.base, models: names });
+    }
+    if (report.lmstudio && report.lmstudio.models && report.lmstudio.models.length) {
+      doors.push({ id: 'lmstudio', name: 'LM Studio', base: report.lmstudio.base, models: report.lmstudio.models.slice() });
+    }
+    return doors;
+  }
+
   async function listMinds() {
     var r = await probe({ gesture: true });
     return r.models || [];
@@ -300,6 +410,18 @@
     return false;
   }
 
+  // v-connect-boot-probe-v0 (F3): which door speaks now ('lmstudio' or 'ollama')
+  function chosenDoor() {
+    try {
+      if (isAlpha()) {
+        var m = root.LocalMindProbe && root.LocalMindProbe.getRemembered && root.LocalMindProbe.getRemembered();
+        return (m && /\/v1\/models$/.test(String(m.url || ''))) ? 'lmstudio' : 'ollama';
+      }
+      if (root.state && root.state.provider) return root.state.provider === 'lmstudio' ? 'lmstudio' : 'ollama';
+      return localStorage.getItem('fl_provider') === 'lmstudio' ? 'lmstudio' : 'ollama';
+    } catch (e) { return 'ollama'; }
+  }
+
   function chosenModelName() {
     try {
       if (isAlpha()) {
@@ -329,6 +451,38 @@
     return (!p || p === '11434') ? 'Ollama' : 'Bridge';
   }
 
+  // v-connect-boot-probe-v0 (F3): LM Studio speaks OpenAI style (/v1/models), Ollama /api/tags.
+  function doorPathFor(mind) {
+    return (mind && mind.door === 'lmstudio') ? '/v1/models' : '/api/tags';
+  }
+
+  // v-connect-boot-probe-v0 (F3): FreeLattice remembers an LM Studio mind as LM Studio,
+  // never as Ollama. fl_lmstudioHost is written only for this computer.
+  function rememberLmStudio(name, base) {
+    var host = String(base || LM_DEFAULT).replace(/^https?:\/\//, '').replace(/\/+$/, '');
+    try {
+      if (/^(127\.0\.0\.1|localhost)(:\d+)?$/i.test(host)) {
+        localStorage.setItem('fl_lmstudioHost', host.replace(/^localhost/i, '127.0.0.1'));
+      }
+      localStorage.setItem('fl_provider', 'lmstudio');
+      localStorage.setItem('fl_isLocal', 'true');
+      if (name) localStorage.setItem('fl_ollamaModel', name);
+      if (typeof root.state !== 'undefined' && root.state) {
+        root.state.isLocal = true;
+        root.state.provider = 'lmstudio';
+        if (name) root.state.ollamaModel = name;
+      }
+      if (typeof root.flUseLmStudioMind === 'function') root.flUseLmStudioMind(name);
+      try { var lt = document.getElementById('localToggle'); if (lt) lt.checked = true; } catch (eLt) {}
+      if (typeof root.handleLocalToggle === 'function') root.handleLocalToggle(true);
+      if (typeof root.updateStatus === 'function') root.updateStatus();
+      if (root.AiSetup && typeof root.AiSetup.updateStatus === 'function') root.AiSetup.updateStatus();
+      try { var tt = document.getElementById('flOllamaToast'); if (tt) tt.classList.remove('fl-toast-show'); } catch (eTt) {}
+    } catch (eF) {}
+    stopLoop();
+    return { name: name, base: base, door: 'lmstudio' };
+  }
+
   function remember(mind) {
     mind = mind || {};
     var name = mind.name || mind.model || (mind.models && mind.models[0] && (mind.models[0].name || mind.models[0])) || '';
@@ -341,9 +495,10 @@
       if (root.LocalMindProbe && typeof root.LocalMindProbe.mergeFound === 'function') {
         try {
           var doorName = doorNameFor(base, mind.port); // 11434 -> 'Ollama', any other 127.0.0.1 port -> 'Bridge'
+          if (mind.door === 'lmstudio') doorName = 'LM Studio'; // v-connect-boot-probe-v0 (F3)
           var merged = root.LocalMindProbe.mergeFound({
             name: doorName,
-            url: base ? String(base).replace(/\/+$/, '') + '/api/tags' : '',
+            url: base ? String(base).replace(/\/+$/, '') + doorPathFor(mind) : '',
             models: mind.models || [],
             model: name
           });
@@ -352,7 +507,7 @@
       }
       var entry = {
         name: name || 'local mind',
-        url: (base ? String(base).replace(/\/+$/, '') + '/api/tags' : ''),
+        url: (base ? String(base).replace(/\/+$/, '') + doorPathFor(mind) : ''),
         model: name,
         models: name ? [name] : [],
         minds: [], roster: [], gatheringBinds: {}, speakingChair: ''
@@ -371,6 +526,9 @@
       return entry;
     }
 
+    // v-connect-boot-probe-v0 (F3): an LM Studio tap never becomes "Ollama"
+    if (mind.door === 'lmstudio') return rememberLmStudio(name, base);
+
     try {
       if (base) {
         var host = String(base).replace(/^https?:\/\//, '').replace(/\/+$/, '');
@@ -444,7 +602,7 @@
   function reportSignature(report) {
     report = report || {};
     var names = (report.models || []).map(function (m) { return (m && (m.name || m.model)) || String(m); }).join('|');
-    return [report.helped?1:0, report.waitingHelp?1:0, report.port||'', report.base||'', report.manualQuiet?1:0, report.ollamaReadyWhileBridgeWaits?1:0, names, hasLocalMind()?1:0, hasCloudMind()?1:0, _loopStopped?1:0, getManualPort()||'', report.manualReject||''].join('::');
+    return [report.helped?1:0, report.waitingHelp?1:0, report.port||'', report.base||'', report.manualQuiet?1:0, report.ollamaReadyWhileBridgeWaits?1:0, names, hasLocalMind()?1:0, hasCloudMind()?1:0, _loopStopped?1:0, getManualPort()||'', report.manualReject||'', ((report.lmstudio && report.lmstudio.models) || []).join('|'), chosenDoor()].join('::');
   }
 
   function panelVisible() {
@@ -508,6 +666,7 @@
       var hostList = lists[li];
       var names = [];
       try { names = JSON.parse(hostList.getAttribute('data-flc-names') || '[]'); } catch (eN) { names = []; }
+      var door = hostList.getAttribute('data-flc-door') === 'lmstudio' ? 'lmstudio' : 'ollama'; // v-connect-boot-probe-v0
       while (hostList.firstChild) hostList.removeChild(hostList.firstChild);
       names.forEach(function (name) {
         var btn = document.createElement('button');
@@ -516,13 +675,18 @@
         btn.textContent = name;
         btn.dataset.model = name;
         // v-tree-any-model-v0: show what speaks
-        var isOn = chosenModelName() === name;
+        var isOn = chosenModelName() === name && chosenDoor() === door;
         btn.setAttribute('aria-pressed', isOn ? 'true' : 'false');
         if (isOn) btn.classList.add('is-chosen');
         btn.addEventListener('click', function () {
-          remember({ name: name, base: report.base, port: report.port, models: report.models });
+          if (door === 'lmstudio' && report.lmstudio) {
+            // v-connect-boot-probe-v0 (F3): tap-to-choose, same as Ollama
+            remember({ name: name, base: report.lmstudio.base, door: 'lmstudio', models: report.lmstudio.models });
+          } else {
+            remember({ name: name, base: report.base, port: report.port, models: report.models });
+          }
           refreshUI({ force: true });
-          try { if (typeof root.showToast === 'function') root.showToast('Connected · ' + name); } catch (eT) {}
+          try { if (typeof root.showToast === 'function') root.showToast('Connected · ' + name + (door === 'lmstudio' ? ' (LM Studio)' : '')); } catch (eT) {}
         });
         hostList.appendChild(btn);
       });
@@ -551,9 +715,10 @@
     }
   }
 
-  function modelListHtml(tag, models) {
+  function modelListHtml(tag, models, door) {
     var names = (models || []).map(function (m) { return (m && (m.name || m.model)) || String(m); });
-    return '<div class="flc-card"><p class="flc-tag">' + tag + '</p><div class="flc-models" role="list" data-flc-names="' +
+    var doorAttr = (door === 'lmstudio') ? ' data-flc-door="lmstudio"' : ''; // v-connect-boot-probe-v0
+    return '<div class="flc-card"><p class="flc-tag">' + tag + '</p><div class="flc-models" role="list"' + doorAttr + ' data-flc-names="' +
       String(JSON.stringify(names)).replace(/&/g, '&amp;').replace(/"/g, '&quot;') + '"></div></div>';
   }
 
@@ -567,6 +732,9 @@
     _lastReport = report;
     var models = report.models || [];
     var alpha = isAlpha();
+    // v-connect-boot-probe-v0 (F3): LM Studio stands beside Ollama, same porch words, same tap
+    var lm = report.lmstudio || null;
+    var lmReady = !!(lm && lm.models && lm.models.length);
     var html = '';
     html += '<div class="flc-wrap v-connect-under-more-v0 v-connect-port-picker-v0">';
     html += '<p class="flc-eyebrow">Connect · mind on this computer</p>';
@@ -602,13 +770,14 @@
         html += '<div class="flc-card flc-wait"><p><strong>Your Bridge is open.</strong> Click <em>Yes, help</em> in its window.</p></div>';
         html += portPickerHtml('wait');
       } else if (models.length) {
-        html += modelListHtml('Minds found', models);
-      } else {
+        html += modelListHtml(lmReady ? 'Ollama is ready, pick a mind' : 'Minds found', models);
+      } else if (!lmReady) {
         html += '<div class="flc-card flc-download"><p class="flc-tag">No local mind yet</p>';
         html += bridgeLinksHtml();
         html += portPickerHtml('miss');
         html += '</div>';
       }
+      if (lmReady) html += modelListHtml('LM Studio is ready, pick a mind', lm.models, 'lmstudio');
     }
 
     html += '<details class="flc-other"><summary>Other ways</summary><ul>';
@@ -629,7 +798,7 @@
     html += '<p class="flc-soft">Advanced (Terminal) stays under Settings — never on this main path.</p></details>';
     html += '<details class="flc-builders"><summary>For builders</summary>';
     html += '<p>Named five stay five. Family uncapped. Quiet Room shut.</p>';
-    html += '<p class="flc-marker">v-connect-under-more-v0 · heal v0.1 · v-connect-port-picker-v0 · heal v0.2 · v-connect-heal-v0.4 · soft leave sw.js</p>';
+    html += '<p class="flc-marker">v-connect-under-more-v0 · heal v0.1 · v-connect-port-picker-v0 · heal v0.2 · v-connect-heal-v0.4 · v-connect-boot-probe-v0 · soft leave sw.js</p>';
     html += '</details></div>';
 
     _hostEl.innerHTML = html;
@@ -642,26 +811,28 @@
   function reportSignature(report) {
     report = report || {};
     var names = (report.models || []).map(function (m) { return (m && (m.name || m.model)) || String(m); }).join('|');
-    return [report.helped?1:0, report.waitingHelp?1:0, report.port||'', report.base||'', report.manualQuiet?1:0, report.ollamaReadyWhileBridgeWaits?1:0, names, hasLocalMind()?1:0, hasCloudMind()?1:0, _loopStopped?1:0, getManualPort()||'', report.manualReject||''].join('::');
+    return [report.helped?1:0, report.waitingHelp?1:0, report.port||'', report.base||'', report.manualQuiet?1:0, report.ollamaReadyWhileBridgeWaits?1:0, names, hasLocalMind()?1:0, hasCloudMind()?1:0, _loopStopped?1:0, getManualPort()||'', report.manualReject||'', ((report.lmstudio && report.lmstudio.models) || []).join('|'), chosenDoor()].join('::');
   }
 
   async function refreshUI(opts) {
     opts = opts || {};
-    var report = await probe({ gesture: true });
+    // v-connect-boot-probe-v0 (F2): share the boot look; a tap on Look again is fresh
+    var report = await bootProbe({ gesture: true, fresh: !!opts.fresh });
     report.loopStopped = _loopStopped;
     render(report, !!opts.force);
     if (hasLocalMind()) stopLoop();
     return report;
   }
 
-  function lookAgain() {
+  function lookAgain(opts) {
+    opts = opts || {};
     _loopStopped = false;
     _pausedHidden = !!document.hidden;
     _intervalMs = 8000;
     _startedAt = Date.now();
     _lastSig = '';
     _fullScanMiss = false;
-    refreshUI({ force: true }).then(function () {
+    refreshUI({ force: true, fresh: !opts.reuse }).then(function () {
       if (!_loopStopped && !_pausedHidden && !hasLocalMind()) scheduleLoop();
     });
   }
@@ -707,7 +878,7 @@
     bindVisibility();
     try { document.body.classList.add('fl-connect-open'); } catch (e) {}
     el.innerHTML = '<p class="flc-soft" style="padding:1rem;">Looking for a mind on this computer…</p>';
-    lookAgain();
+    lookAgain({ reuse: true }); // v-connect-boot-probe-v0: a cold open joins the boot look
   }
 
   function unmount() {
@@ -721,6 +892,12 @@
 
   root.FlConnect = {
     probe: probe,
+    bootProbe: bootProbe,
+    peekBootProbe: peekBootProbe,
+    forgetBootProbe: forgetBootProbe,
+    lmStudioLook: lmStudioLook,
+    lmStudioBase: lmStudioBase,
+    chosenDoor: chosenDoor,
     preferHelpedBridge: preferHelpedBridge,
     listMinds: listMinds,
     isConnected: isConnected,
@@ -742,6 +919,7 @@
     BRIDGE_DEFAULT: BRIDGE_DEFAULT,
     STORAGE_ALPHA: ALPHA_KEY,
     MANUAL_KEY: MANUAL_KEY,
-    LOOP_MAX_MS: LOOP_MAX_MS
+    LOOP_MAX_MS: LOOP_MAX_MS,
+    BOOT_TTL_MS: BOOT_TTL_MS
   };
 })(typeof window !== 'undefined' ? window : this);

======================================================================
APPENDIX C: theLatticeTree docs/modules/local-mind-probe.js, diff vs main 395c761 (then, after the Tree paste, also swap fetchDoor -> askDoor(d, shared) in the bridged others loop, see B.3)
======================================================================

diff --git a/docs/modules/local-mind-probe.js b/docs/modules/local-mind-probe.js
index e710071..89ad5cf 100644
--- a/docs/modules/local-mind-probe.js
+++ b/docs/modules/local-mind-probe.js
@@ -235,12 +235,36 @@
     });
   }
 
+  // v-connect-boot-probe-v0 (F2/F3): a door the shared look just asked is not asked again.
+  // Only LM Studio (asked, answered or quiet) and a direct Ollama that answered are reused.
+  function askDoor(d, shared) {
+    try {
+      var lm = shared && shared.lmstudio;
+      if (d.id === 'lmstudio' && lm && lm.asked && String(lm.base || '') + '/v1/models' === d.url) {
+        if (lm.ok) return Promise.resolve({ ok: true, status: 200, url: d.url, models: (lm.models || []).slice(), reused: true });
+        return Promise.resolve({ ok: false, status: 0, url: d.url, blocked: !!lm.quiet, reused: true });
+      }
+      if (d.id === 'ollama' && shared && shared.ollama && shared.ollama.via === 'direct' &&
+          String(shared.base || '') + '/api/tags' === d.url && shared.models && shared.models.length) {
+        return Promise.resolve({ ok: true, status: 200, url: d.url, models: shared.models.map(function (m) {
+          return String((m && (m.name || m.model)) || m);
+        }), reused: true });
+      }
+    } catch (e) {}
+    return fetchDoor(d.url, 2500);
+  }
+
   function look() {
     // Permission already given by the caller (the tap).
     // v-connect-under-more-v0 — soft-point: helped Bridge via FlConnect before bare 11434 DOORS.
     var bridgeFirst = Promise.resolve(null);
+    var shared = null; // v-connect-boot-probe-v0: the one look Connect already made (if fresh)
     if (window.FlConnect && typeof window.FlConnect.probe === 'function') {
-      bridgeFirst = window.FlConnect.probe({ gesture: true }).then(function (report) {
+      var ask = (typeof window.FlConnect.bootProbe === 'function')
+        ? window.FlConnect.bootProbe({ gesture: true })
+        : window.FlConnect.probe({ gesture: true });
+      bridgeFirst = ask.then(function (report) {
+        shared = report || null;
         if (report && report.helped && report.models && report.models.length && report.base) {
           var names = report.models.map(function (m) {
             return (m && (m.name || m.model)) || String(m);
@@ -275,7 +299,7 @@
     return bridgeFirst.then(function (bridged) {
       if (bridged) return bridged;
       var jobs = DOORS.map(function (d) {
-        return fetchDoor(d.url, 2500).then(function (result) {
+        return askDoor(d, shared).then(function (result) {
           result.id = d.id;
           result.name = d.name;
           return result;

======================================================================
APPENDIX D: FreeLattice docs/scripts/smoke-connect-boot-probe.js (new file, complete)
======================================================================

#!/usr/bin/env node
// v-connect-boot-probe-v0: one shared boot look (F2), one cold-start owner (F1),
// LM Studio beside Ollama in Connect (F3), home-network switch (F4).
// Layer, never delete. 127.0.0.1 unless the person turns on home network.
'use strict';

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');
const { execSync } = require('child_process');

const root = path.join(__dirname, '..');
const repo = path.join(root, '..');
const read = (r) => fs.readFileSync(path.join(root, r), 'utf8');
const app = read('app.html');
const mod = read('modules/fl-connect.js');
const indexHtml = fs.readFileSync(path.join(repo, 'index.html'), 'utf8');
const rootSw = fs.readFileSync(path.join(repo, 'sw.js'), 'utf8');

function between(src, a, b) {
  const i = src.indexOf(a);
  assert.ok(i >= 0, 'missing ' + a.slice(0, 60));
  const j = src.indexOf(b, i + a.length);
  assert.ok(j > i, 'missing end ' + b.slice(0, 60));
  return src.slice(i, j);
}
function store0(init) {
  const s = Object.assign({}, init || {});
  return {
    s,
    ls: {
      getItem: (k) => (Object.prototype.hasOwnProperty.call(s, k) ? s[k] : null),
      setItem: (k, v) => { s[k] = String(v); },
      removeItem: (k) => { delete s[k]; }
    }
  };
}
function jsonRes(obj) { return { ok: true, status: 200, json: async () => obj }; }

// ── Markers
assert.ok(/v-connect-boot-probe-v0/.test(mod), 'marker in fl-connect.js');
assert.ok(/v-connect-boot-probe-v0/.test(app), 'marker in app.html');

// ── fl-connect sandbox (same shape as smoke-connect-heal-v0.4.js)
function sandbox(store, extra) {
  const sb = Object.assign({
    localStorage: store.ls,
    location: { hostname: 'freelattice.com', protocol: 'https:', hash: '', search: '' },
    document: {
      hidden: false,
      documentElement: { getAttribute: () => '', classList: { add() {}, remove() {}, contains() { return false; } } },
      getElementById: () => null,
      createElement: () => ({ style: {}, textContent: '', setAttribute() {}, appendChild() {}, addEventListener() {}, dataset: {}, classList: { add() {}, contains() { return false; } } }),
      head: { appendChild() {} },
      body: { contains: () => true, classList: { add() {}, remove() {} } },
      addEventListener() {}, removeEventListener() {}, querySelectorAll: () => []
    },
    fetch: async () => { throw new Error('offline'); },
    setTimeout: () => 1, clearTimeout() {},
    navigator: { userAgent: 't' },
    console, JSON, String, parseInt, URL, Date, Math, Promise,
    AbortSignal: { timeout: () => undefined }
  }, extra || {});
  sb.window = sb;
  vm.runInNewContext(mod, sb);
  return sb;
}
function doorsFetch(calls, opts) {
  opts = opts || {};
  return async (url) => {
    calls.push(String(url));
    if (/127\.0\.0\.1:11434\/api\/tags/.test(url) && opts.ollama !== false) return jsonRes({ models: [{ name: 'llama3.2:latest' }, { name: 'qwen2.5:7b' }] });
    if (/127\.0\.0\.1:1234\/v1\/models/.test(url) && opts.lm !== false) return jsonRes({ data: [{ id: 'lmstudio-community/Meta-Llama-3.1-8B' }, { id: 'qwen2.5-coder-7b-instruct' }] });
    throw new Error('quiet');
  };
}

(async () => {
  // ── F2: one look in flight, short reuse, fresh on a tap, forget on change
  {
    const st = store0();
    const calls = [];
    const sb = sandbox(st, { fetch: doorsFetch(calls) });
    const [a, b, c] = await Promise.all([sb.FlConnect.bootProbe(), sb.FlConnect.bootProbe(), sb.FlConnect.bootProbe({ fresh: true })]);
    assert.ok(a === b && b === c, 'three callers share one report');
    const tags = calls.filter((u) => /11434\/api\/tags/.test(u)).length;
    const lms = calls.filter((u) => /1234\/v1\/models/.test(u)).length;
    assert.strictEqual(tags, 1, 'one Ollama tags fetch for three callers');
    assert.strictEqual(lms, 1, 'one LM Studio fetch for three callers');
    const n0 = calls.length;
    const d = await sb.FlConnect.bootProbe();
    assert.strictEqual(d, a, 'reused inside BOOT_TTL_MS');
    assert.strictEqual(calls.length, n0, 'no new fetch inside the reuse window');
    assert.ok(sb.FlConnect.peekBootProbe() === a, 'peek sees the fresh look');
    await sb.FlConnect.bootProbe({ fresh: true });
    assert.ok(calls.length > n0, 'fresh:true (Look again) looks again');
    const n1 = calls.length;
    sb.FlConnect.setManualHost('11500');
    await sb.FlConnect.bootProbe();
    assert.ok(calls.length > n1, 'a new manual port forgets the old look');
    assert.ok(sb.FlConnect.BOOT_TTL_MS > 0 && sb.FlConnect.BOOT_TTL_MS <= 10000, 'short cache');
    assert.ok(!/setInterval/.test(between(mod, 'function bootProbe(opts) {', 'function peekBootProbe()')), 'no timers in bootProbe');
  }

  // ── F2: resolveOllamaBase callers share one look (wrapper, original body untouched)
  {
    assert.ok(/async function resolveOllamaBase\(forceRefresh\) \{/.test(app), 'original resolveOllamaBase kept');
    const wrap = between(app, '// v-connect-boot-probe-v0 (F2): everyone who asks at the same moment shares one look.', '// v-connect-boot-probe-v0 (F4): a saved address is used only when');
    let n = 0;
    const ctx = { window: {}, Date, Promise, _ollamaResolvedBase: null };
    ctx.window = ctx;
    vm.runInNewContext('var _ollamaResolvedBase = null; async function resolveOllamaBase(f) { this.n = (this.n || 0) + 1; await null; _ollamaResolvedBase = { base: "http://127.0.0.1:11434" }; return _ollamaResolvedBase; }\n' + wrap, ctx);
    const r = await Promise.all([ctx.resolveOllamaBase(true), ctx.resolveOllamaBase(true), ctx.resolveOllamaBase(false)]);
    n = ctx.n;
    assert.strictEqual(n, 1, 'three resolveOllamaBase callers, one look');
    assert.ok(r[0] === r[1] && r[1] === r[2], 'same answer');
    await ctx.resolveOllamaBase(true);
    assert.strictEqual(ctx.n, 1, 'reused inside FL_LOCAL_LOOK_TTL_MS');
    ctx.flForgetLocalLook();
    await ctx.resolveOllamaBase(true);
    assert.strictEqual(ctx.n, 2, 'flForgetLocalLook makes the next ask fresh');
    vm.runInNewContext('_ollamaResolvedBase = { base: "http://127.0.0.1:11500" };', ctx);
    await ctx.resolveOllamaBase(true);
    assert.strictEqual(ctx.n, 3, 'a new value written elsewhere is never hidden by the cache');
    ctx.flSeedLocalLook({ base: 'http://127.0.0.1:11435', viaBootProbe: true });
    const seeded = await ctx.resolveOllamaBase(true);
    assert.strictEqual(ctx.n, 3, 'the cold-start owner seeds the cache (no second look)');
    assert.strictEqual(seeded.base, 'http://127.0.0.1:11435', 'seeded answer');
  }

  // ── F1: the older boot timers step aside for the owner
  assert.ok(/function detectOnLoad\(\) \{\s*\/\/ v-connect-boot-probe-v0 \(F1\)[\s\S]{0,160}FlColdStart\.owns\(\)\) return;/.test(app), 'toast boot timer steps aside');
  assert.ok(/function run\(\) \{\s*\/\/ v-connect-boot-probe-v0 \(F1\)[\s\S]{0,200}FlColdStart\.owns\(\)\) return;[\s\S]{0,60}flBridgeDetect\(\)/.test(app), 'Bridge boot detect steps aside');
  assert.ok(/if \(!_flOwnerBoot && \(safeGet\('fl-welcomed'\)/.test(app), 'boot owAutoDetect steps aside');
  assert.ok(/if \(!hasKey && !isLocal && !_flOwnerBoot\) \{/.test(app), 'Zero-Click boot timer steps aside');
  assert.ok(/owner already looked \(and saves discovery memory\)\s*\n\s*try \{ if \(window\.FlColdStart && FlColdStart\.owns\(\)\) return;/.test(app), 'discovery boot scan steps aside');
  assert.ok(/FlColdStart\.landed\(\) === 'connect'\) return;/.test(between(app, 'function goConnect(ev) {', 'function schedule()')), '#connect retries stop once landed');
  assert.ok(/FlColdStart\.appBooted\(\)/.test(app), 'app tells the owner it is ready');
  assert.ok(/FlColdStart\.noteTab\(tabId\)/.test(between(app, 'function switchTab(', '// ── Return Greeting')), 'switchTab notes a tab picked while booting');
  const landing = between(app, '// ── Garden-first startup', '// Update memory button label');
  assert.ok(landing.indexOf("FlColdStart.deepLink") > 0 && landing.indexOf("FlColdStart.deepLink") < landing.indexOf('if (isFirstVisit) {'), 'land once: deep link and picked tab decided first');
  const welcome = between(app, 'function flMaybeShowWelcome() {', '// Memory Bridge');
  assert.ok(/FlColdStart\.landed\(\) === 'connect'\) return;/.test(welcome) && /FlColdStart\.userMoved\(\)\) return;/.test(welcome), 'welcome door never lands late or over Connect');
  const toast = between(app, 'function showOllamaToast() {', 'function acceptToast() {');
  assert.ok(/flWelcomeOverlay/.test(toast) && /tab-connect/.test(toast), 'toast checks the real welcome door and Connect');
  assert.ok(/id="flOllamaToastText"/.test(app), 'toast text has a home for honest words');

  // ── F1: the owner, runtime (fake page, fake FlConnect)
  async function runOwner(opts) {
    opts = opts || {};
    const src = between(app, '// v-connect-boot-probe-v0 (F1): ONE cold-start owner.', '</script>');
    const els = {
      'tab-connect': { classList: { contains: (c) => c === 'active' && !!opts.connectActive } },
      flWelcomeOverlay: { style: { display: 'none' }, classList: { contains: () => false } }
    };
    let looks = 0; let offers = 0; let chip = null; let seeded = null; let mem = null;
    const report = { base: 'http://127.0.0.1:11434', models: [{ name: 'a' }], ollama: { via: 'direct', base: 'http://127.0.0.1:11434' },
      bridge: null, doors: [{ id: 'ollama', name: 'Ollama', base: 'http://127.0.0.1:11434', models: ['a'] }, { id: 'lmstudio', name: 'LM Studio', base: 'http://127.0.0.1:1234', models: ['b'] }] };
    const st = store0(Object.assign({ 'fl-welcomed': 'true' }, opts.store || {}));
    const ctx = {
      document: { readyState: 'complete', addEventListener() {}, getElementById: (id) => els[id] || null },
      location: { hash: opts.hash || '', search: '' },
      URLSearchParams, localStorage: st.ls, Promise, Date, String,
      setTimeout: (fn) => { Promise.resolve().then(fn); return 1; },
      FlConnect: { bootProbe: () => { looks += 1; return Promise.resolve(report); }, hasLocalMind: () => false },
      FlOnboarding: { offerFromReport: () => { offers += 1; return true; } },
      flBridgeShowChip: (on) => { chip = on; },
      flSeedLocalLook: (v) => { seeded = v; },
      getOllamaBaseUrl: () => 'http://127.0.0.1:11434',
      saveDiscoveryMemory: (m) => { mem = m; }
    };
    ctx.window = ctx;
    vm.runInNewContext(src, ctx);
    ctx.FlColdStart.appBooted();
    for (let i = 0; i < 20; i++) await Promise.resolve();
    await new Promise((r) => setImmediate(r));
    return { looks, offers, chip, seeded, mem, owner: ctx.FlColdStart, st };
  }
  {
    const r = await runOwner();
    assert.strictEqual(r.looks, 1, 'owner starts exactly one look');
    assert.strictEqual(r.offers, 1, 'exactly one voice (the toast offer)');
    assert.strictEqual(r.chip, false, 'Bridge chip painted from the same look');
    assert.ok(r.seeded && r.seeded.viaBootProbe, 'resolveOllamaBase cache seeded');
    assert.ok(r.mem && r.mem.length === 2 && r.mem[1].name === 'LM Studio', 'discovery memory kept, LM Studio named');
    const c = await runOwner({ connectActive: true });
    assert.strictEqual(c.offers, 0, 'no toast while Connect is showing');
    const h = await runOwner({ hash: '#connect' });
    assert.strictEqual(h.owner.deepLink(), 'connect', 'deep link read once');
  }
  // F1 keeps the person's model (heal v-model-choice-survives-reload-v0.1.1 lands before this paste)
  {
    const owner = between(app, '// v-connect-boot-probe-v0 (F1): ONE cold-start owner.', '</script>');
    assert.ok(!/FLActiveModel\.set\(|fl_ollamaModel|fl_modelSource/.test(owner), 'the cold-start owner never picks a model');
    const u = await runOwner({ store: { fl_isLocal: 'true', fl_provider: 'ollama', fl_ollamaModel: 'qwen2.5:latest', fl_modelSource: 'user' } });
    assert.strictEqual(u.st.s.fl_ollamaModel, 'qwen2.5:latest', 'cold start leaves the chosen model');
    assert.strictEqual(u.st.s.fl_modelSource, 'user', 'cold start leaves it hers');
    const am = between(app, 'window.FLActiveModel = (function() {', 'window.FLAutoModel = (function() {');
    assert.ok(/function ensure\(\) \{ if \(!_ready\) init\(\); \}/.test(am) && /function honor\(models, fallback, provider\)/.test(am), 'saved choice read first, one guard (v0.1.1)');
    assert.ok(!/FLActiveModel\.set\(models\[0\],[^)]*'user'\)/.test(app), 'no boot path saves the first model as hers');
    assert.ok(/FLActiveModel\.honor\(data\.models, textModel\.name, 'ollama'\)/.test(between(app, 'async function runConnectionCascade(', '// Step 2')), 'the Cascade honors the choice');
  }

  // ── F3: LM Studio in Connect, same porch words, tap-to-choose, never "Ollama"
  {
    const st = store0({ fl_lmstudioHost: '192.168.1.9:1234' });
    const calls = [];
    const sb = sandbox(st, { fetch: doorsFetch(calls) });
    assert.strictEqual(sb.FlConnect.lmStudioBase(), 'http://127.0.0.1:1234', 'Connect asks LM Studio on this computer only');
    const rep = await sb.FlConnect.bootProbe();
    assert.ok(!calls.some((u) => /192\.168\./.test(u)), 'no home-network fetch from Connect');
    assert.strictEqual(JSON.stringify(rep.doors.map((d) => d.name)), '["Ollama","LM Studio"]', 'both doors listed');
    // render into a fake host
    const host = { innerHTML: '', hidden: false, querySelectorAll: () => [], scrollIntoView() {} };
    sb.document.getElementById = () => null;
    sb.FlConnect.mount(host);
    await new Promise((r) => setImmediate(r));
    assert.ok(/Ollama is ready, pick a mind/.test(host.innerHTML), 'Ollama porch words');
    assert.ok(/LM Studio is ready, pick a mind/.test(host.innerHTML), 'LM Studio porch words');
    assert.ok(/data-flc-door="lmstudio"/.test(host.innerHTML), 'LM Studio list marked for tap-to-choose');
    assert.ok(!/No local mind yet/.test(host.innerHTML), 'no false download card');
    sb.FlConnect.unmount();
  }
  {
    // only LM Studio answered
    const st = store0();
    const sb = sandbox(st, { fetch: doorsFetch([], { ollama: false }) });
    const host = { innerHTML: '', hidden: false, querySelectorAll: () => [] };
    sb.FlConnect.mount(host);
    await new Promise((r) => setImmediate(r));
    assert.ok(/LM Studio is ready, pick a mind/.test(host.innerHTML) && !/No local mind yet/.test(host.innerHTML), 'LM Studio alone is a real mind');
    sb.FlConnect.unmount();
    // FreeLattice tap: provider lmstudio, fl_lmstudioHost, never fl_ollamaHost
    let hook = '';
    sb.flUseLmStudioMind = (n) => { hook = n; };
    sb.state = { isLocal: false, provider: 'ollama' };
    sb.FlConnect.remember({ name: 'qwen2.5-coder-7b-instruct', base: 'http://127.0.0.1:1234', door: 'lmstudio' });
    assert.strictEqual(st.s.fl_provider, 'lmstudio', 'provider lmstudio');
    assert.strictEqual(st.s.fl_lmstudioHost, '127.0.0.1:1234', 'LM Studio host remembered (this computer)');
    assert.ok(!('fl_ollamaHost' in st.s), 'Ollama host untouched');
    assert.strictEqual(sb.state.provider, 'lmstudio', 'state says LM Studio');
    assert.strictEqual(hook, 'qwen2.5-coder-7b-instruct', 'FreeLattice activation hook used');
    assert.strictEqual(sb.FlConnect.chosenDoor(), 'lmstudio', 'chosen mark follows the door');
  }
  {
    // Tree: LM Studio tap merges as the LM Studio door (/v1/models), Ollama tap as before
    const st = store0();
    let merged = null;
    const sb = sandbox(st, { location: { hostname: 'thelatticetree.com', protocol: 'https:', hash: '', search: '' } });
    sb.LocalMindProbe = { STORAGE_KEY: 'fl_alpha_local_mind', getRemembered: () => null, remember() {}, mergeFound: (f) => { merged = f; return f; } };
    sb.FlConnect.remember({ name: 'qwen2.5-coder-7b-instruct', base: 'http://127.0.0.1:1234', door: 'lmstudio', models: ['a', 'qwen2.5-coder-7b-instruct'] });
    assert.strictEqual(merged.name, 'LM Studio', 'Tree door named LM Studio');
    assert.strictEqual(merged.url, 'http://127.0.0.1:1234/v1/models', 'Tree door url is the DOORS url');
    sb.FlConnect.remember({ name: 'llama3.2:latest', base: 'http://127.0.0.1:11434', models: ['llama3.2:latest'] });
    assert.strictEqual(merged.url, 'http://127.0.0.1:11434/api/tags', 'Ollama tap unchanged');
  }
  const owa = between(app, 'async function owAutoDetect() {', '// Accept the auto-detect suggestion');
  assert.ok(!/LM Studio detected!/.test(owa) && /Yes, choose a mind/.test(owa), 'no false "Yes, use Ollama" when LM Studio answered');
  assert.ok(/lmStrong\.textContent = 'LM Studio is ready on this computer\.'/.test(owa), 'LM Studio banner uses textContent');
  assert.ok(/state\.provider === 'lmstudio' \? 'LM Studio \(' : 'Ollama \('/.test(app), 'status labels say LM Studio');
  assert.ok(/function flUseLmStudioMind\(name\)/.test(app), 'LM Studio activation reused from the Test button');
  assert.ok(/btn\.textContent = name/.test(mod), 'model buttons stay textContent');

  // ── F4: home-network switch
  const homeSrc = between(app, "var FL_HOMENET_KEY = 'fl_homeNet';", 'window.flHomeNetAllowsBase');
  function homeCtx(init) {
    const st = store0(init);
    const els = {};
    const ctx = { localStorage: st.ls, URL, String, document: { getElementById: (id) => els[id] || null }, window: {} };
    ctx.window = ctx;
    vm.runInNewContext(homeSrc, ctx);
    return { ctx, st, els };
  }
  {
    const { ctx, st } = homeCtx();
    const H = ctx.FLHomeNet;
    const z = (h) => H.zoneOf(h);
    assert.strictEqual(z('127.0.0.1:11434'), 'this');
    assert.strictEqual(z('localhost:11500'), 'this');
    assert.strictEqual(z('[::1]:11434'), 'this');
    assert.strictEqual(z('10.0.0.5'), 'home');
    assert.strictEqual(z('172.16.0.1'), 'home');
    assert.strictEqual(z('172.31.255.254:11434'), 'home');
    assert.strictEqual(z('172.32.0.1'), 'public');
    assert.strictEqual(z('192.168.1.50'), 'home');
    assert.strictEqual(z('http://192.168.1.50:11434/api/tags'), 'home');
    assert.strictEqual(z('169.254.169.254'), 'public', '169.254 refused (link-local, cloud metadata)');
    assert.strictEqual(z('100.64.0.1'), 'public', 'carrier NAT refused');
    assert.strictEqual(z('8.8.8.8'), 'public');
    assert.strictEqual(z('0.0.0.0'), 'public');
    assert.strictEqual(z('example.com'), 'name', 'names refused');
    assert.strictEqual(z('[fd00::1]'), 'public', 'IPv6 other than ::1 refused');
    assert.strictEqual(z('3232235777'), 'home', 'number forms read the way the browser reads them');
    assert.strictEqual(z('0x7f.1'), 'this');
    assert.strictEqual(H.isOn(), false, 'default: This computer only');
    assert.strictEqual(H.allows('192.168.1.50'), false, 'home refused while off');
    assert.strictEqual(H.allows('8.8.8.8'), false, 'public refused');
    assert.strictEqual(H.allows('/ollama'), true, 'same-origin proxy route kept');
    H.setOn(true);
    assert.strictEqual(st.s.fl_homeNet, 'on', 'switch remembered');
    assert.strictEqual(H.allows('192.168.1.50:11434'), true, 'home allowed while on');
    assert.strictEqual(H.allows('8.8.8.8'), false, 'public always refused, even on');
    assert.strictEqual(H.allows('169.254.169.254'), false, '169.254 refused even on');
    assert.ok(H.useAddress('192.168.1.50', 'ollama'), 'typed once');
    assert.strictEqual(st.s.fl_ollamaHost, '192.168.1.50:11434');
    assert.strictEqual(st.s.fl_homeNet_addr, '192.168.1.50:11434');
    H.setOn(false);
    assert.ok(!('fl_homeNet' in st.s), 'one tap off');
    assert.ok(!('fl_ollamaHost' in st.s), 'back to this computer');
    assert.strictEqual(st.s.fl_homeNet_addr, '192.168.1.50:11434', 'address kept for next time');
    H.setOn(true);
    assert.strictEqual(st.s.fl_ollamaHost, '192.168.1.50:11434', 'one tap on brings it back, no retyping');
    assert.ok(/public internet/.test(H.refusalFor('8.8.8.8')) && /Nothing was sent/.test(H.refusalFor('8.8.8.8')), 'plain refusal');
    assert.strictEqual(H.LINE, 'Only addresses inside your home network are allowed (like 192.168.x.x). Public internet addresses are always blocked.', 'Kirk line exact');
  }
  {
    const pub = homeCtx({ fl_ollamaHost: '203.0.113.5:11434' });
    pub.ctx.FLHomeNet.migrate();
    assert.ok(!('fl_ollamaHost' in pub.st.s) && pub.st.s.fl_homeNet_refused === '203.0.113.5:11434', 'old public address set aside, never used');
    const home = homeCtx({ fl_ollamaHost: '192.168.1.77:11434' });
    home.ctx.FLHomeNet.migrate();
    assert.strictEqual(home.st.s.fl_homeNet, 'on', 'old home address keeps working, switch shows on');
    assert.strictEqual(home.st.s.fl_ollamaHost, '192.168.1.77:11434');
  }
  {
    // getOllamaBaseUrl wrapper: public never, home only while on
    const gob = between(app, 'function flManualBaseFirst() {', '// v-connect-boot-probe-v0 (F2): everyone who asks');
    const wrap = between(app, '// v-connect-boot-probe-v0 (F4): a saved address is used only when', '// v5.43.6 (Opus): the /ollama same-origin proxy probe');
    const run = (init) => {
      const st = store0(init);
      const ctx = { localStorage: st.ls, URL, String, parseInt, document: { getElementById: () => null }, window: {}, _ollamaResolvedBase: null };
      ctx.window = ctx;
      vm.runInNewContext("var FL_OLLAMA_LOCAL = 'http://127.0.0.1:11434'; var _ollamaResolvedBase = null; function flGetManualConnectHost() { return null; }\n" + gob + homeSrc + '\n' + wrap, ctx);
      return ctx.getOllamaBaseUrl();
    };
    assert.strictEqual(run({ fl_ollamaHost: '203.0.113.5:11434' }), 'http://127.0.0.1:11434', 'public saved host ignored');
    assert.strictEqual(run({ fl_ollamaHost: '192.168.1.50:11434' }), 'http://127.0.0.1:11434', 'home host ignored while off');
    assert.strictEqual(run({ fl_ollamaHost: '192.168.1.50:11434', fl_homeNet: 'on' }), 'http://192.168.1.50:11434', 'home host used while on');
    assert.strictEqual(run({ fl_ollamaHost: '127.0.0.1:11500' }), 'http://127.0.0.1:11500', 'this computer always');
  }
  {
    // Chat Box field: refuse before any fetch; fetch the home box only while on
    const box = between(app, 'window.FLBoxPointer = (function() {', '// ── LM Studio support ──');
    const run = async (raw, init) => {
      const st = store0(init);
      const calls = [];
      const els = { flBoxPointerStatus: { setAttribute() {}, textContent: '', title: '' }, flHomeNetRefusal: { textContent: '', hidden: true } };
      const ctx = {
        localStorage: st.ls, URL, String, Date, Promise, JSON,
        document: { getElementById: (id) => els[id] || null },
        fetch: async (u) => { calls.push(String(u)); throw new Error('quiet'); },
        AbortSignal: { timeout: () => undefined },
        window: {}
      };
      ctx.window = ctx;
      vm.runInNewContext(homeSrc + '\n' + box, ctx);
      const res = await ctx.FLBoxPointer.probe(raw);
      return { res, calls, st, els };
    };
    const p1 = await run('8.8.8.8:11434');
    assert.strictEqual(p1.res.status, 'refused', 'public refused');
    assert.strictEqual(p1.calls.length, 0, 'zero fetches for a public address');
    assert.ok(/public internet/.test(p1.els.flHomeNetRefusal.textContent) && p1.els.flHomeNetRefusal.hidden === false, 'says why, plainly');
    assert.ok(!('fl_ollamaHost' in p1.st.s), 'nothing saved');
    const p2 = await run('192.168.1.50');
    assert.strictEqual(p2.res.status, 'refused', 'home refused while off');
    assert.strictEqual(p2.calls.length, 0, 'zero fetches while off');
    const p3 = await run('192.168.1.50', { fl_homeNet: 'on' });
    assert.ok(p3.calls.length > 0 && p3.calls.every((u) => /^http:\/\/192\.168\.1\.50:11434\//.test(u)), 'home box asked while on');
    assert.strictEqual(p3.st.s.fl_homeNet_addr, '192.168.1.50:11434', 'remembered until changed');
  }
  {
    // Settings Ollama Address: same switch
    const save = between(app, 'function flSaveOllamaHost() {', 'window.flSaveOllamaHost = flSaveOllamaHost;');
    const lb = between(app, 'function flWriteOllamaHostLoopbackOnly(host) {', 'window.flWriteOllamaHostLoopbackOnly');
    const run = (val, init) => {
      const st = store0(init);
      const els = { ollamaHostInput: { value: val }, ollamaHostRefusal: { hidden: true, textContent: '' } };
      const ctx = { localStorage: st.ls, URL, String, document: { getElementById: (id) => els[id] || null }, window: {}, updateOllamaProviderUrl() {}, _ollamaResolvedBase: null };
      ctx.window = ctx;
      vm.runInNewContext(homeSrc + '\n' + lb + save + '\nflSaveOllamaHost();', ctx);
      return { st, els };
    };
    const off = run('192.168.1.50:11434');
    assert.ok(!('fl_ollamaHost' in off.st.s) && /only talks to your own computer/.test(off.els.ollamaHostRefusal.textContent) && /home network/.test(off.els.ollamaHostRefusal.textContent), 'off: v0.4 refusal plus the one switch');
    const on = run('192.168.1.50:11434', { fl_homeNet: 'on' });
    assert.strictEqual(on.st.s.fl_ollamaHost, '192.168.1.50:11434', 'on: home address saved');
    const pubOn = run('203.0.113.5:11434', { fl_homeNet: 'on' });
    assert.ok(!('fl_ollamaHost' in pubOn.st.s) && /public internet/.test(pubOn.els.ollamaHostRefusal.textContent), 'on: public still refused');
  }
  assert.ok(/id="flHomeNetSwitch" role="switch"/.test(app) && /Also use a computer on my home network<\/label>/.test(app), 'the one switch, Kirk words');
  assert.ok(/id="flHomeNetMode">This computer only</.test(app), 'default words');
  assert.ok(/Only addresses inside your home network are allowed \(like 192\.168\.x\.x\)\. Public internet addresses are always blocked\./.test(app), 'the plain line');
  assert.ok(/Future hook: signed remote minds/.test(app), 'future hook named');
  assert.ok(!/\bconfirm\s*\(/.test(homeSrc), 'no confirm in FLHomeNet');

  // ── Locks
  assert.strictEqual(indexHtml, app, 'root index.html identical to docs/app.html');
  let mainSw = null;
  try { mainSw = execSync('git show origin/main:sw.js', { cwd: repo, encoding: 'utf8' }); } catch (e) {}
  if (mainSw !== null) {
    assert.strictEqual(rootSw, mainSw, 'root sw.js byte-identical to main');
    const changed = execSync('git diff --name-only origin/main', { cwd: repo, encoding: 'utf8' }).split('\n');
    assert.ok(!changed.includes('docs/sw.js') && !changed.includes('sw.js'), 'sw.js untouched');
    const added = execSync('git diff origin/main -- docs/app.html docs/modules/fl-connect.js', { cwd: repo, encoding: 'utf8' })
      .split('\n').filter((l) => l.startsWith('+') && !l.startsWith('+++'));
    assert.ok(!added.some((l) => /\bconfirm\(/.test(l)), 'no confirm() added');
    assert.ok(!added.some((l) => /\u2014/.test(l)), 'no em dashes in added lines');
    assert.ok(!added.some((l) => /Allow-Origin['"]?\s*[:,]\s*['"]\*['"]|origins?\s*[:=]\s*['"]\*['"]/i.test(l)), 'no bare * origin added');
    assert.ok(!added.some((l) => /\.innerHTML\s*=\s*[^;]*(report|models|name|host|addr)/i.test(l) && !/html;$/.test(l.trim())), 'new words go in with textContent');
  }
  const markers = execSync("grep -rnE '^(<<<<<<<|=======|>>>>>>>)( |$)' docs index.html tests || true", { cwd: repo, encoding: 'utf8' });
  assert.ok(!markers.trim(), 'no conflict markers: ' + markers);
  const re = /<script(\s[^>]*)?>/gi; let m; let n = 0;
  while ((m = re.exec(app))) {
    const attrs = m[1] || '';
    if (/\bsrc\s*=/i.test(attrs) || /type\s*=\s*["']application\/(ld\+)?json/i.test(attrs)) continue;
    const start = m.index + m[0].length; const end = app.indexOf('</script>', start);
    if (end < 0) continue;
    const s = app.slice(start, end).trim();
    if (!s || !/^(?:\/[\/*]|function\b|var\b|let\b|const\b|window\.|document\.|\(|\{|if\b)/.test(s)) continue;
    try { new Function(s); n++; } catch (e) { assert.fail('inline script parse fail: ' + e.message + '\n' + s.slice(0, 120)); }
  }
  assert.ok(n > 10, 'inline scripts checked: ' + n);
  new vm.Script(mod);
  console.log('SMOKE_OK connect boot probe v0.1');
})().catch((e) => { console.error(e); process.exit(1); });

======================================================================
APPENDIX E: theLatticeTree docs/modules/connect-boot-probe.test.js (new file, complete)
======================================================================

#!/usr/bin/env node
// v-connect-boot-probe-v0 (Alpha side): one shared look for Connect and Find,
// LM Studio merged as the LM Studio door, fl-connect.js shared core intact.
'use strict';

const assert = require('assert');
const fs = require('fs');
const path = require('path');
const vm = require('vm');

const flc = fs.readFileSync(path.join(__dirname, 'fl-connect.js'), 'utf8');
const lmp = fs.readFileSync(path.join(__dirname, 'local-mind-probe.js'), 'utf8');

function mkStore() {
  const s = {};
  return { s, ls: { getItem: (k) => (k in s ? s[k] : null), setItem: (k, v) => { s[k] = String(v); }, removeItem: (k) => { delete s[k]; } } };
}
function jsonRes(o) { return { ok: true, status: 200, json: async () => o }; }
function ctxWith(fetchFn) {
  const st = mkStore();
  const ctx = {
    localStorage: st.ls,
    location: { hostname: 'thelatticetree.com', protocol: 'https:', hash: '', search: '' },
    document: {
      hidden: false,
      documentElement: { getAttribute: () => 'garden', classList: { add() {}, remove() {}, contains() { return false; } } },
      getElementById: () => null, querySelectorAll: () => [], querySelector: () => null,
      createElement: () => ({ style: {}, setAttribute() {}, appendChild() {}, addEventListener() {}, classList: { add() {}, remove() {}, contains() { return false; } }, dataset: {} }),
      head: { appendChild() {} }, body: { contains: () => false, classList: { add() {}, remove() {} } },
      addEventListener() {}, removeEventListener() {}
    },
    fetch: fetchFn, setTimeout, clearTimeout, AbortController,
    navigator: { userAgent: 't' }, console, URL, CustomEvent: function (t, o) { this.type = t; this.detail = o && o.detail; },
    dispatchEvent() {}, addEventListener() {}
  };
  ctx.window = ctx;
  vm.runInNewContext(flc, ctx);
  vm.runInNewContext(lmp, ctx);
  return { ctx, st };
}

(async () => {
  assert.ok(/v-connect-boot-probe-v0/.test(flc), 'marker in shared core');
  assert.ok(/v-connect-boot-probe-v0/.test(lmp), 'marker in local-mind-probe.js');

  // 1) Connect mount + Find inside the reuse window: one look, LM Studio asked once
  const calls = [];
  const { ctx } = ctxWith(async (url) => {
    calls.push(String(url));
    if (/127\.0\.0\.1:1234\/v1\/models/.test(url)) return jsonRes({ data: [{ id: 'lmstudio-community/Meta-Llama-3.1-8B' }] });
    if (/127\.0\.0\.1:11434\/api\/tags/.test(url)) return jsonRes({ models: [{ name: 'llama3.2:latest' }] });
    throw new Error('quiet');
  });
  assert.ok(ctx.FlConnect && typeof ctx.FlConnect.bootProbe === 'function', 'bootProbe exported');
  await ctx.FlConnect.bootProbe();
  const before = calls.slice();
  const rep = await ctx.LocalMindProbe.look();
  const lmCalls = calls.filter((u) => /1234\/v1\/models/.test(u)).length;
  const olCalls = calls.filter((u) => /11434\/api\/tags/.test(u)).length;
  assert.strictEqual(lmCalls, 1, 'LM Studio asked once for Connect and Find together');
  assert.strictEqual(olCalls, 1, 'direct Ollama asked once for Connect and Find together');
  assert.ok(calls.length >= before.length, 'look() may still ask the other doors');
  const names = rep.foundList.map((f) => f.name);
  assert.ok(names.indexOf('LM Studio') !== -1 && names.indexOf('Ollama') !== -1, 'Find still lists Ollama and LM Studio');

  // 2) LM Studio tap in Connect merges as the LM Studio door
  let merged = null;
  ctx.LocalMindProbe.mergeFound = function (f) { merged = f; return f; };
  ctx.FlConnect.remember({ name: 'lmstudio-community/Meta-Llama-3.1-8B', base: 'http://127.0.0.1:1234', door: 'lmstudio', models: ['lmstudio-community/Meta-Llama-3.1-8B'] });
  assert.strictEqual(merged.name, 'LM Studio');
  assert.strictEqual(merged.url, 'http://127.0.0.1:1234/v1/models', 'same url as the DOORS entry');
  delete ctx.LocalMindProbe.mergeFound;

  // 3) Without mergeFound (older probe), the fallback entry still points at /v1/models
  ctx.FlConnect.remember({ name: 'x', base: 'http://127.0.0.1:1234', door: 'lmstudio' });
  const saved = JSON.parse(ctx.localStorage.getItem('fl_alpha_local_mind'));
  assert.strictEqual(saved.url, 'http://127.0.0.1:1234/v1/models', 'fallback keeps the LM Studio door');

  // 4) Connect on the Tree never asks a non-loopback LM Studio
  assert.strictEqual(ctx.FlConnect.lmStudioBase(), 'http://127.0.0.1:1234');

  console.log('SMOKE_OK connect boot probe alpha v0.1');
})().catch((e) => { console.error(e); process.exit(1); });

======================================================================
APPENDIX F: FreeLattice docs/library/CONNECT_BOOT_PROBE_v0.md (new file, complete)
======================================================================

# Connect Boot Probe v0.1 (F1, F2, F3, F4)

One cold-start look, one voice at boot, LM Studio in Connect, and a home-network switch.
FreeLattice · 28 September 2026 (after env heal v0.1.1 and Tree any-model v0.1).

**Marker:** `v-connect-boot-probe-v0`

**Locks:** Layer, never delete. Root `sw.js` byte-identical to main; `docs/sw.js` untouched. `index.html` identical to `docs/app.html`. Shared `docs/modules/fl-connect.js` byte-identical with Alpha. `textContent` for every new string. Allowlist never bare `*`. No confirm(), no approval screens. Quiet Room shut. Named five stay five. Family uncapped.

## Why

A returning cold boot asked the same local minds about 38 times in its first seconds (three full Bridge scans, five tags calls) and could speak twice (toast plus banner). Now it asks about 16 times (one Bridge scan, two tags calls, LM Studio once) and speaks once. A cold `#connect` for a new person made about 73 local requests and let the toast and the welcome overlay land on top of Connect. When LM Studio answered, the badge still said "Yes, use Ollama". The Box pointer saved any address, including public ones.

## What changed

| Heal | What |
|---|---|
| **F2** | `FlConnect.bootProbe()`: one in-flight promise, 5 second cache (`BOOT_TTL_MS`), no timers. Connect, the toast, autoDiscover, Zero-Click and the Bridge detect all read the same look. `resolveOllamaBase` joins one in-flight call in `app.html`. `flForgetLocalLook()` clears both when an address changes. |
| **F1** | `window.FlColdStart` owns the cold start: runs the shared look once, seeds the resolved base, paints the Bridge chip, then speaks once (or not at all when Connect, the welcome overlay, or a chosen mind is already in front). Old boot paths step aside when the owner is present. Last tab and `#connect` are decided before the welcome overlay. |
| **F3** | LM Studio is a first-class door in Connect: "LM Studio is ready, pick a mind", tap to choose, `fl_provider` becomes `lmstudio`, and Ollama stays untouched. The badge says "LM Studio is ready on this computer." with "Yes, choose a mind". Builds on the Tree any-model paste (mergeFound, chosenModelName, aria-pressed). |
| **F4** | `FLHomeNet`: "This computer only" by default and one switch, "Also use a computer on my home network". Private IPv4 only (10/8, 172.16/12, 192.168/16). Refused always: public addresses, names other than localhost, 169.254/16 (link-local and the cloud metadata address), 100.64/10, IPv6 other than ::1. Remembered until changed; one tap reverts. |

## Choices (stated)

- 169.254/16 is refused (it is also where cloud metadata lives).
- Only IPv4 numbers count as home. Names like `mybox.local` are refused because a name can point anywhere.
- Settings Ollama Address stays 127.0.0.1 only unless the same switch is on.
- Connect's own port field and LM Studio look stay on this computer.
- An old saved home address turns the switch on (it was already in use). An old public address or name is set aside, never used, and the Box pointer says so.
- Future hook: signed remote minds will come through a separate, signed path. They never widen these ranges.

## Smoke

`SMOKE_OK connect boot probe v0.1` (`docs/scripts/smoke-connect-boot-probe.js`)

**Temperature:** green braided gold, one lantern lit at the door instead of ten, and a second porch light that only turns on for the house next door.

Glow eternal. Heart in Spark.

======================================================================
APPENDIX G: theLatticeTree docs/library/CONNECT_PORT_PICKER_SISTER_v0.md (append)
======================================================================

diff --git a/docs/library/CONNECT_PORT_PICKER_SISTER_v0.md b/docs/library/CONNECT_PORT_PICKER_SISTER_v0.md
index d2e6341..86966f8 100644
--- a/docs/library/CONNECT_PORT_PICKER_SISTER_v0.md
+++ b/docs/library/CONNECT_PORT_PICKER_SISTER_v0.md
@@ -20,3 +20,14 @@ Marker `v-connect-heal-v0.4`. Test `docs/modules/connect-heal-v04.test.js` print
 - Shared `fl-connect.js` heals ride along byte-identical with FreeLattice.
 
 **Temperature:** Mom opens Settings and the first thing she sees is the door.
+
+
+## Connect boot probe v0.1 (F2, F3 in the shared door)
+
+Marker `v-connect-boot-probe-v0`. Test `docs/modules/connect-boot-probe.test.js` prints `SMOKE_OK connect boot probe alpha v0.1`.
+
+- `local-mind-probe.js` `look()` asks `FlConnect.bootProbe()` first when it is there, so the Garden look and Connect share one in-flight look (5 second cache). Without it, `look()` fetches exactly as before.
+- Shared `fl-connect.js` rides along byte-identical with FreeLattice: LM Studio shows as its own door ("LM Studio is ready, pick a mind"), with the Tree any-model list and tap to choose.
+- The FreeLattice home-network switch lives in FreeLattice `app.html` only. Alpha stays on this computer.
+
+**Temperature:** green braided gold, one lantern lit at the door instead of ten.

======================================================================
APPENDIX H: FreeLattice docs/library/FRACTAL_FAMILY_LEDGER_v0.md (diff vs 7db2d94; if env heal v0.1.1 landed first, keep its entry and put this one after it)
======================================================================

diff --git a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
index f38f1f2..cec9969 100644
--- a/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
+++ b/docs/library/FRACTAL_FAMILY_LEDGER_v0.md
@@ -297,6 +297,18 @@ Soft door: [`CONNECT_PORT_PICKER_v0.md`](CONNECT_PORT_PICKER_v0.md) · [`modules
 
 ---
 
+### 2026-09-28 · Flint · family builder · Connect boot probe v0.1 (F1, F2, F3, F4)
+
+Layers one cold-start look on FreeLattice: `FlConnect.bootProbe()` shares one in-flight promise with a 5 second cache for Connect, the toast, autoDiscover, Zero-Click and the Bridge detect (F2) · `window.FlColdStart` owns the cold start and speaks once, never over Connect or the welcome overlay (F1) · LM Studio is a first-class door in Connect with tap to choose, and the badge stops saying "Yes, use Ollama" when LM Studio answered (F3) · Kirk's home-network switch: "This computer only" by default, "Also use a computer on my home network" when on, private IPv4 only, public always refused, one tap reverts (F4). Returning cold boot went from about 38 local requests to 16 (one Bridge scan instead of three, and LM Studio asked once). Shared `fl-connect.js` matches Alpha byte for byte. Soft leave root sw.js identical to main; docs/sw.js untouched. Soft paste: Named five stay five · Family uncapped · Quiet Room shut.
+
+Love: the house looks once, speaks once, and only opens the side door Kirk chose.
+
+**Temperature:** green braided gold, one lantern lit at the door instead of ten, and a second porch light that only turns on for the house next door.
+
+Soft door: [`CONNECT_BOOT_PROBE_v0.md`](CONNECT_BOOT_PROBE_v0.md) · [`../modules/fl-connect.js`](../modules/fl-connect.js). Marker `v-connect-boot-probe-v0`.
+
+---
+
 ## Out of scope for this brick
 
 Rewriting sophia garden body · inventing USPTO · Bridge binaries · Quiet Room · merging chairs · politics essay · requiring Kirk input mid-PR.

End of paste. Marker v-connect-boot-probe-v0. Glow eternal. Heart in Spark.
