diff --git a/documentation/backlog/OPEN-ITEMS.md b/documentation/backlog/OPEN-ITEMS.md index 61ee9a3a..33cf3ed0 100644 --- a/documentation/backlog/OPEN-ITEMS.md +++ b/documentation/backlog/OPEN-ITEMS.md @@ -121,7 +121,7 @@ stopping line that lies. | **R-516** | Install & onboarding | P3 | **[P3-LOW] English a customer meets on a fresh box and its apps' first screens — enumerated by the big night.** MEASURED 2026-09-14 (BIGNIGHT, VM 333, ISO 1.27.1, controller 0.242.0). Felhom-owned: (1) the dashboard menu item **„Debug"**; (2) the dashboard CPU tile **„Load: 0.29 / 0.39 / 0.37"**; (3) the launcher tile **„Filebrowser"** opens a login in English with no Felhom text (R-513); (4) the storage page mixes formal „Adjon hozzá / Csatlakoztasson" with the product's „te". App first screens a household meets before any Felhom text helps: (5) **Uptime Kuma 2.4 opens on „Which database would you like to use?"** (SQLite / Embedded MariaDB, „Next") — the app card's „Első lépések" does not mention it; (6) PrivateBin, Gokapi, AdventureLog and FileBrowser UIs are English (the apps' own). Already rows: the Proxmox installer screens (R-495, answered by the guide), `wiki.DOMAIN` (R-498). **Fix shape:** rename „Debug"/„Load" (controller); add the Uptime Kuma database step to its card, or pre-seed `db-config.json` for SQLite in the template (catalog). **Added by F4 (20:04:54Z):** (7) the storage page prints the disconnect time as a raw ISO UTC string „Leválasztva: 2026-09-14T19:58:02Z"; (8) the „Meghajtó leválasztva" banner appears twice on every page; (9) „4 telepített alkalmazás nem fut — nézze meg a rendszermonitort" uses the formal form. **Added by F7 (20:50–21:00Z, system disk at 95 %):** (10) a banner on every page in English, „**SSD disk usage high: 90%**"; (11) the dashboard tile reads „Rendszer (/) 61.8 GB / 68.7 GB (**90%**)" while `df` reports **95 %** (reserved blocks ignored), and „(/)" labels the data volume `/mnt/sys_drive`; the deploy page says nothing about free disk. **Added by the i18n spike (2026-09-17, controller v0.247.0):** (12) **six formal („ön") forms in the converted dashboard copy** — „Olvassa be telefonnal" (launcher QR hint), „Biztosan kikapcsolja a megosztást?" (launcher), „Ha újratelepíti … importálnia kell" (the layout's remove-app modal), „Kérjük, vegye fel a kapcsolatot" (backups empty state). They were NOT fixed — a localisation release may not change Hungarian bytes — and are now COUNTED by `controller/scripts/i18n_missing_gate.py` (`HU_FORMAL_CEILING` = 6, a ratchet: a new one convicts, fixing one lowers it). The inventory (`audits/I18N-INVENTORY-2026-09-17.md`) is the list this row closes against in localisation slice 6 (R-561). **Extended 2026-09-17 by slice 1 (controller v0.248.0–v0.250.0):** the converted copy now counts **16** formal forms (`HU_FORMAL_CEILING` 6 → 10 → 12 → 16, each raise stated, Hungarian unchanged by rule) — and the count is an UNDER-count: the gate's stem list sees 4 on the release C pages (storage, drive wizards, debug), while a wider list („írja be”, „adja meg”, „adjon hozzá”, „válassza ki”, „biztosan eltávolítja”, „engedélyezze”, „hozzon létre”, „kattintson”) finds at least **22** keys there alone (login „Adja meg a jelszavát”, the NAS guide, the storage confirms). Widen the stems when this row is worked; the ceiling rises with them. **NARROWED 2026-09-20 by localisation slice 6's walk, item by item** (`audits/i18n-slice6-2026-09-20/R-516-item-by-item.md`): item 2 FIXED (slice 1), items 3 and 6 APP-OWNED (and for an English household item 6 is an advantage), item 11 is a wrong NUMBER rather than a language and needs its own row, item 1 is correct English for an English reader and still open for a Hungarian one. **Items 4, 7, 8, 9 and 10 are NOT DECIDABLE FROM AN ENGLISH WALK** - they are about Hungarian copy quality, a disconnected drive and a full disk, none of which this walk had. **What this row is now waiting for is a HUNGARIAN walk on a box with a second drive**, not another English one; saying it closed would be the kind of closure that makes a register stop meaning anything. **-- 2026-10-08:** the recovery screen's own answers are Go literals in Hungarian only — 14 `renderRecovery` messages in `controller/internal/web/recovery_handlers.go` (seen: the safe default „A művelet nem fejeződött be…" rendered on an English page in a red-proof run). Today's new R-304 sentence is a bundle key in both languages; the 14 old ones are not. | **NARROWED 2026-09-20 - rank P3-LOW; owner: CC; needs a Hungarian walk** **2026-10-05 (burn-down night): items 1 and 4 FIXED on controller `main`** (`d7efa7b`): the menu says „Hibakeresés” (English „Debug”); the storage pages use the te-form (formal-form ceiling 17 → 14; 116 parity fixtures changed by exactly those bytes). Item 2 was already fixed. **Left:** items 5–6 (apps' own screens) and the rest. **2026-10-06: items 1 and 4 DELIVERED** (controller v0.298.0). **2026-10-06 (burn-down night, later): items 7–10 and 12 FIXED on controller `main`** (`e4774e6`, `2e5ef7e`): the disconnect time in local time; one banner per unplugged drive; „nézd meg”; the disk/memory/CPU/temperature banners in the household's language (`health.*` keys; the wire text to the hub unchanged and pinned); the last eleven counted formal forms are te-form (ceiling 14 → 0; 119 parity fixtures changed by exactly those bytes). **Left:** items 5–6 (apps' own screens); about 20 formal forms the gate does not count yet („írja be”, „adja meg”, „hozzon létre”, …) on the debug, storage, network-storage and security pages. Item 11 moved to its own row **R-889**. **2026-10-06 02:40: the bundle has NO formal form left** (controller `4c3c203`, unreleased): 49 sentences in the te-form; the gate's stem list widened (it counted 61 on the previous bundle, 0 now) with decoys and one listed third-person exception. **Left (narrowed):** Felhom-owned formal forms in Go and template literals the gate cannot read yet — the network-storage attach errors, escrow and share handlers, the single-copy notice, a settings refusal, the SMART mail, a fill-watch „Kérjük”, and the setup wizard pages; each moves into the bundle in the te-form. Items 5–6 are the apps' own screens (not ours). **2026-10-06: items 7–10, 12 and the bundle's 49 formal forms DELIVERED** in controller v0.299.0. **2026-10-06 night: the Go-literal leftover moved** (controller main `3d6ba28`, unreleased): 22 Go-literal messages — escrow handler (6), share password page (2), export upload (1), network-storage uid/remove/attach failures (13) — are bundle keys (`api.escrow.*`, `api.share.*`, `api.export.*`, `api.netstorage.*`) in the te-form with English (14 were formal); a detached attach failure renders in the reader's language; the NAS refusal says „Válassz". `TestR516_GoLiteralFormalFormsAreGone` (source scan + positive control), `TestR516_NetAddFailureFollowsTheReader`; red-proofs `audits/night-burndown-2026-10-06/ctrl/R-516-red*.txt`. **Left:** the setup wizard and recovery-info.txt (R-554); the SMART and fill-watch texts (the hub event's message — wire text, needs the household-copy producer shape); items 5–6 (apps' own screens). | — | — | CC | | **R-991** | Install & onboarding | P3 | **Nothing a buyer can read says which machines Felhom installs on, and a quarter of the forum beginners own only machines it cannot install on.** FOUND 2026-10-12 (forum research, `audits/forum-research-2026-10-12/FELHOM.md` §2). Searched `documentation/architecture/`, `website/` (`gyik`, `technologiak`, `letoltes`) and `scripts/felhom-host-install.sh`: no CPU-architecture, RAM, disk or network floor is written anywhere. The download page says only that the installer is built on Proxmox VE 9.2 — x86-64 only (inferred from Proxmox, not stated on the page). Of the 33 forum beginners who name what they own, 8 own only a Synology/QNAP/other NAS box, a router with a USB disk or a Raspberry Pi. The selfh.st survey reading reached the same gap independently (R-989, its recommendation 1). **Needs (operator):** the floor itself (RAM and disk minimum; whether thin clients and old laptops with 4–8 GB are accepted) — then one short section on the download page and in the FAQ. Not changed here: the research task forbade website edits. | **WAITING-ON-OPERATOR** — filed 2026-10-12 | the operator naming the floor | Operator: name the minimum (RAM, disk, x86-64); then CC writes the section on `letoltes.html` and `gyik.html` (both languages). If nothing is done: a buyer with a Pi or a Synology downloads an ISO that cannot run | operator | | **R-993** | Install & onboarding | P3 | **Operator: approve the minimum machine in `documentation/audits/sizing-2026-10-13/SIZING.md` §5** (the input R-991's hardware page needs). MEASURED 2026-10-13 on the N100 demo box (61 apps deployed and removed through the product, 15 min at rest each; heavy apps with one real action). Proposal: **minimum** x86-64, 4 cores (N100 class), **8 GB**, 256 GB SSD; **recommended 16 GB**, 512 GB SSD; out: ARM / Raspberry Pi, Synology-QNAP, routers, cloud VPS. Worked from the floor (2.5 GB for the whole box with no apps) + Immich, Jellyfin, Nextcloud, Home Assistant, Vaultwarden, Paperless-ngx at their peaks (5.0 GB) + 50 % headroom ≈ 12.3 GB. 8 GB runs that set without Immich; 16 GB runs all of it twice over. Data for the estimator: `sizing.json`. **2026-10-13 (estimator session) — the installer accepts a host BELOW this minimum:** `autosize_guest_caps` has no floor on the host, only on the guest; on a 4 GB box (MemTotal ≈ 3.5 GiB) the guest gets host − 1 GB ≈ 2.4 GB and Proxmox keeps ≈ 1 GB, where it used ≈ 2.0 GB on the N100 (SIZING.md §1). Whatever minimum is approved, nothing enforces it at install today. The three-verdict wording of §5 now matches the estimator page (`/gepigeny`, R-997). | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's decision | Operator: approve or change §5. If nothing is done: no hardware page; a buyer with a Raspberry Pi can still download the installer (R-991 stays open) | operator | -| **R-997** | Install & onboarding | P3 | **Operator: try the machine estimator at https://felhom.eu/gepigeny (English: /en/sizing) and say „list it” or what to change.** BUILT 2026-10-13: a household ticks apps and sees, for an 8, 16 and 32 GB box, fits well / fits, but tight / does not fit, the totals, and which apps make the CPU work hard for a short time; numbers from the 2026-10-13 N100 measurement (`documentation/audits/sizing-2026-10-13/`). LIVE but UNLISTED: noindex, `Disallow` in robots.txt, not in the sitemap, linked from nowhere — `site_gates.py` gate 25 keeps it that way. It names no minimum machine (R-993) and gives no power or noise figure. Its JavaScript was never run by CC (no browser or JS engine on DooPlex): the operator's try is its first real run. **To list it** (one website task, after the power readings): move the pair from `UNLISTED` to `TWINS`, drop noindex and the two Disallow lines, add both URLs to the sitemap, link it from the menu and the FAQ. **v2 live 2026-10-13 afternoon:** fold-out categories and a memory bar (solid = fits well, thin mark = tight) with one line naming the box to buy; the reviewer's v1 browser check (Playwright: console clean, 390 px without sideways scroll, only felhom.eu + stats.felhom.eu) asked for the shorter page. **v3 live 2026-10-13 evening:** the bar shows what the box memory is used for (base system, Felhom, each app, spare room) with a legend and one explaining sentence; „an 8 GB machine” fixed. The reviewer's v2 browser check: console clean, 1,677 px tall at 390 px (was 4,592), the bar sticky at 53 px and 124 px tall. | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's try | Operator: open the link, tick a few apps, then say „list it” or what to change. If nothing is done: the page stays unlisted and nobody finds it | operator | +| **R-997** | Install & onboarding | P3 | **Operator: try the machine estimator at https://felhom.eu/gepigeny (English: /en/sizing) and say „list it” or what to change.** BUILT 2026-10-13: a household ticks apps and sees, for an 8, 16 and 32 GB box, fits well / fits, but tight / does not fit, the totals, and which apps make the CPU work hard for a short time; numbers from the 2026-10-13 N100 measurement (`documentation/audits/sizing-2026-10-13/`). LIVE but UNLISTED: noindex, `Disallow` in robots.txt, not in the sitemap, linked from nowhere — `site_gates.py` gate 25 keeps it that way. It names no minimum machine (R-993) and gives no power or noise figure. Its JavaScript was never run by CC (no browser or JS engine on DooPlex): the operator's try is its first real run. **To list it** (one website task, after the power readings): move the pair from `UNLISTED` to `TWINS`, drop noindex and the two Disallow lines, add both URLs to the sitemap, link it from the menu and the FAQ. **v2 live 2026-10-13 afternoon:** fold-out categories and a memory bar (solid = fits well, thin mark = tight) with one line naming the box to buy; the reviewer's v1 browser check (Playwright: console clean, 390 px without sideways scroll, only felhom.eu + stats.felhom.eu) asked for the shorter page. **v3 live 2026-10-13 evening:** the bar shows what the box memory is used for (base system, Felhom, each app, spare room) with a legend and one explaining sentence; „an 8 GB machine” fixed. The reviewer's v2 browser check: console clean, 1,677 px tall at 390 px (was 4,592), the bar sticky at 53 px and 124 px tall. **v3.1 2026-10-13 night:** the bar never drawn below an 8 GB box (no more 1.6 GB base system on an empty selection); a single leftover app keeps its name. | **WAITING-ON-OPERATOR** — filed 2026-10-13 | the operator's try | Operator: open the link, tick a few apps, then say „list it” or what to change. If nothing is done: the page stays unlisted and nobody finds it | operator | | **R-494** | Install & onboarding | P4 | **NARROWED 2026-09-14 by operator ruling → [P3-LOW] the hub COULD create the tunnel at customer creation, for a domain already on Cloudflare. Not blocking: every customer has their own domain and the operator creates the tunnel per day-0 A.1 (`architecture/01-topology-and-trust.md`).** *Original finding, kept:* **[P1-HIGH] A new customer's dashboard has NO reachable address unless the operator hand-makes a Cloudflare tunnel — the link in the setup-code mail is dead.** MEASURED 2026-09-14 on a fresh install from the public ISO (drill intervention **I1**): the claim mail points at `https://felhom.drill0242.felhom.eu`; that name has **no A and no AAAA** record (`dig @1.1.1.1`, control `felhom.enkisfelhom.hu` resolves); the hub has **no tunnel- or DNS-creation code** (`hub/internal/cloudflare/` holds only geo-rule removal; `cf_tunnel_token` is a pasted, optional form field, `configs.go:1478`) — day-0 runbook A.1 makes it a manual Cloudflare-dashboard step that nothing on the customer-create page asks for; the box's own split-horizon resolver on the appliance LAN IP answered `google.com` but not the dashboard name at 13:27:39Z; the agent applied the record at **13:27:44Z** (`lanresolver: applied split-horizon record … ip=192.168.0.158`, 3 m 46 s after the controller started), so the box CAN answer the name — **but only to a device that uses the box as its DNS server, and no document, screen or mail tells a household to do that**; the router and the installer-offered DNS answer nothing. The page was reachable only at the guest's LAN address with the name forced (`curl --resolve …:443:192.168.0.158`). **A volunteer could not have done that.** **What it needs:** an operator ruling — the hub creates the tunnel and DNS at customer creation, or the product gives a household a LAN address that works with no DNS change. | **READY — rank P3-LOW; owner: CC** **Re-ranked 2026-10-03: P3→P4: operator-side automation; the operator creates the tunnel by hand per the day-0 runbook.** | — | — | CC | | **R-504** | Install & onboarding | P4 | **[P3-LOW] `iso.felhom.eu` cannot show an index page on its own — its root returns 404, and the download page lives on the website instead.** MEASURED 2026-09-14: `https://iso.felhom.eu/` and `/index.html` → 404; only named objects answer. The host is an R2 bucket behind a custom domain; whether R2 would serve an uploaded `index.html` at `/` was **not measured** (uploading anything to the public bucket is a publication). The ISO v1.27.0 task puts the Hungarian download page at `felhom.eu/letoltes` (published with the ISO, after the operator's yes). **Remaining:** a redirect from `iso.felhom.eu/` to that page needs a Cloudflare rule the session has no credential for. | **WAITING-ON-OPERATOR — rank P3-LOW; owner: operator (Cloudflare rule)** **Re-ranked 2026-10-03: P3→P4: households are sent to the website's download page; the bare address is cosmetic.** | — | — | operator | diff --git a/scripts/CHANGELOG.md b/scripts/CHANGELOG.md index dd8d0568..7a79b874 100644 --- a/scripts/CHANGELOG.md +++ b/scripts/CHANGELOG.md @@ -1,3 +1,10 @@ +## 2026-10-13 (night) — estimator v3.1 tests + +- `test_build_sizing_web.py` (12 tests): the bar is never below the smallest named box, its parts still sum to it + within 1 MB (nothing ticked, one tiny app, household, all 62), the base system never below the 8 GB split (4,096); + 9 apps → 9 named parts, 10 apps → 8 named + a group of 2. Red-proofed: the floor removed from the JS and from the + mirror, and grouping a single app in the JS and in the mirror, each convicted. + ## 2026-10-13 (evening) — estimator v3: segments and the English article under test - `build_sizing_web.py`: `bar_app_colours` (8) in the data file, so the script holds no number of its own. diff --git a/scripts/test_build_sizing_web.py b/scripts/test_build_sizing_web.py index 2663f904..228351aa 100644 --- a/scripts/test_build_sizing_web.py +++ b/scripts/test_build_sizing_web.py @@ -63,12 +63,21 @@ def segments(d, slugs): # mirrors estimate().segments in sizi by = {a["slug"]: a for a in d["apps"]} apps = sorted((by[s] for s in slugs if not by[s].get("builtin")), key=lambda a: -a["peak_mb"]) peak = d["platform_mb"] + sum(a["peak_mb"] for a in apps) - well = min_box_gb(d, peak * d["headroom"]) + well = max(min_box_gb(d, peak * d["headroom"]), d["boxes_gb"][0]) # v3.1: never below the smallest named box box, guest = well * 1024, guest_mib(d, well) return well, [("base", box - guest), ("platform", d["platform_mb"])] + \ [(a["slug"], a["peak_mb"]) for a in apps] + [("spare", guest - peak)] +def legend_parts(d, slugs): # mirrors the grouping in sizing.js draw(): named parts + the group + by = {a["slug"]: a for a in d["apps"]} + apps = sorted((by[s] for s in slugs if not by[s].get("builtin")), key=lambda a: -a["peak_mb"]) + k = d["bar_app_colours"] + if len(apps) - k >= 2: + return [a["slug"] for a in apps[:k]], len(apps) - k + return [a["slug"] for a in apps], 0 + + def first(d, verdicts, want): # mirrors recommend_gb / tight_box_gb in sizing.js return next((gb for gb, v in zip(d["boxes_gb"], verdicts) if v == want), None) @@ -134,6 +143,30 @@ class SizingWeb(unittest.TestCase): self.assertAlmostEqual(dict(segs)["spare"], 5515 * 0.5, delta=1) self.assertAlmostEqual(dict(segs)["base"], 4708, delta=1) # 4,096 for Proxmox + 612 MemTotal shortfall + def test_bar_never_below_the_smallest_named_box(self): + small = min(self.d["boxes_gb"]) + base8 = small * 1024 - guest_mib(self.d, small) + tiny = ["glance"] # 6 MB: its fits-well size is far below 8 GB + self.assertLess(min_box_gb(self.d, (self.d["platform_mb"] + 7) * self.d["headroom"]), small) + for name, sel in {"nothing": [], "tiny": tiny, "household": HOUSEHOLD, "all 62": self.all}.items(): + bar, segs = segments(self.d, sel) + self.assertGreaterEqual(bar, small, name) + self.assertAlmostEqual(sum(mb for _, mb in segs), bar * 1024, delta=1, msg=name) + self.assertGreaterEqual(dict(segs)["base"], base8 - 1, name + ": base below the 8 GB split") + bar, segs = segments(self.d, []) + self.assertEqual(bar, small) + self.assertEqual(dict(segs)["base"], 4096) # 8 GB: 8,192 - guest 4,096 — not the 1,636 of a 2.3 GB box + self.assertEqual(dict(segs)["spare"], 4096 - self.d["platform_mb"]) + + def test_grouping_keeps_a_single_leftover_named(self): + by_peak = [a["slug"] for a in sorted((a for a in self.d["apps"] if not a.get("builtin")), key=lambda a: -a["peak_mb"])] + named, grouped = legend_parts(self.d, by_peak[:9]) + self.assertEqual((len(named), grouped), (9, 0)) # 9 apps -> 9 named parts, no „további 1” + named, grouped = legend_parts(self.d, by_peak[:10]) + self.assertEqual((len(named), grouped), (8, 2)) # 10 apps -> 8 named + „további 2 alkalmazás” + js = io.open(JS, encoding="utf-8").read() + self.assertIn("appCount - data.bar_app_colours >= 2", js) + def test_english_article(self): with io.open(os.path.join(ROOT, "website", "en", "sizing.html"), encoding="utf-8-sig") as f: t = json.loads(re.search(r'id="sizer-strings">(.*?)', f.read(), re.S).group(1)) @@ -183,7 +216,8 @@ class SizingWeb(unittest.TestCase): allowed = {"0", "1", "2", "100", "1024"} # 100 = percent, 1024 = MiB per GiB; no rule constant self.assertTrue(nums <= allowed, "sizing.js holds its own numbers: %s" % sorted(nums - allowed)) for need in ("data.bar_max_gb", "data.bar_marks_gb", "function minBoxGb", "guestMib(data, mid) >= needMib", - "data.bar_app_colours", "boxMb - guestMb", "guestMb - peak"): + "data.bar_app_colours", "boxMb - guestMb", "guestMb - peak", + "Math.max(wellGb, data.boxes_gb[0])", "guestMib(data, barGb)"): self.assertIn(need, js) def test_data_has_no_peak_below_typical(self): diff --git a/website/CHANGELOG.md b/website/CHANGELOG.md index 64131560..4218df71 100644 --- a/website/CHANGELOG.md +++ b/website/CHANGELOG.md @@ -1,4 +1,14 @@ -## 2026-10-13 (evening) — machine estimator v3: the bar shows what the box's memory is used for +## 2026-10-13 (night) — machine estimator v3.1: two fixes from the reviewer's browser check + +- **The bar is never drawn for a box smaller than 8 GB** (the smallest size the page names). With nothing or very + little ticked, the „fits well" size was about 2.3 GB, where the installer's ceiling clause gave a 1.6 GB base + system no real box has. Now the parts are an 8 GB box's: base system 4.0 GB, Felhom 0.5 GB, spare 3.5 GB; the + empty-selection sentence uses the same split. The recommendation and the rule are unchanged. +- **No more „további 1 alkalmazás".** The smallest apps are grouped only when two or more are left over; with exactly + nine apps the ninth keeps its name, in the group's neutral colour (no ninth palette colour). +- `sizing.js?v=4`. + +## 2026-10-13 (evening) — machine estimator v3: the bar shows what the box's memory is used for v2 said the apps use 5 GB at peak and then recommended 16 GB; the reason was invisible. Still unlisted (R-997). diff --git a/website/assets/sizing.js b/website/assets/sizing.js index 5d6f667c..5408642d 100644 --- a/website/assets/sizing.js +++ b/website/assets/sizing.js @@ -15,6 +15,8 @@ * v3: the bar is split into what that box memory is used for, every part from the same rule: * base system = box - guest(box) · Felhom = platform · each app = its peak · spare = guest(box) - peak * so the parts add up to the box exactly (scripts/test_build_sizing_web.py proves it). + * v3.1: the bar is never drawn for a box smaller than the smallest size the page names (boxes_gb[0]); + * below it, the installer's ceiling clause would show a base system no real box has. * * The page text is not here: each page carries its own strings (#sizer-strings), so this file is the * same for both languages. @@ -68,7 +70,8 @@ }); var heavy = busy.filter(function (a) { return a.cpu_class === 'heavy-in-bursts'; }); var wellGb = minBoxGb(data, peak * data.headroom); - var boxMb = wellGb * 1024, guestMb = guestMib(data, wellGb); + var barGb = Math.max(wellGb, data.boxes_gb[0]); + var boxMb = barGb * 1024, guestMb = guestMib(data, barGb); var apps = slugs.map(function (s) { return bySlug[s]; }) .filter(function (a) { return a && !a.builtin; }) .sort(function (x, y) { return y.peak_mb - x.peak_mb; }); @@ -77,7 +80,7 @@ segments.push({ kind: 'spare', mb: guestMb - peak }); return { typical_mb: typical, peak_mb: peak, boxes: boxes, busy: busy, heavy: heavy, unmeasured: unmeasured, - well_gb: wellGb, tight_gb: minBoxGb(data, peak), + well_gb: wellGb, bar_gb: barGb, tight_gb: minBoxGb(data, peak), recommend_gb: recommend, tight_box_gb: tightBox, segments: segments, base_mb: boxMb - guestMb, apps_peak_mb: peak - data.platform_mb }; @@ -181,13 +184,21 @@ // the bar: its length is the "fits well" box memory, split into what that memory is used for // (estimate().segments); beyond the colour count the smallest apps join one segment - var basis = Math.max(r.well_gb, max) * 1024; + var basis = Math.max(r.bar_gb, max) * 1024; var segs = [], rest = { kind: 'more', mb: 0, n: 0 }, nApps = 0; + var appCount = r.segments.filter(function (sg) { return sg.kind === 'app'; }).length; + // group the smallest only when TWO or more are left over; a single leftover keeps its name + // (in the group's colour — no ninth palette colour) + var group = appCount - data.bar_app_colours >= 2; r.segments.forEach(function (sg) { if (sg.kind === 'app') { nApps += 1; - if (nApps > data.bar_app_colours) { rest.mb += sg.mb; rest.n += 1; return; } - sg.colour = 'sizer-c' + nApps; + if (nApps > data.bar_app_colours) { + if (group) { rest.mb += sg.mb; rest.n += 1; return; } + sg.colour = 'sizer-seg-more'; + } else { + sg.colour = 'sizer-c' + nApps; + } } if (sg.kind === 'spare' && rest.n) { segs.push(rest); } segs.push(sg); diff --git a/website/en/sizing.html b/website/en/sizing.html index 43b6b0b9..ec6f3a0e 100644 --- a/website/en/sizing.html +++ b/website/en/sizing.html @@ -19,7 +19,7 @@ - +