diff --git a/plans/sketches/2026-09-18-names-lookup-flows.excalidraw b/plans/sketches/2026-09-18-names-lookup-flows.excalidraw index f67948f1cc..4c31e896fb 100644 --- a/plans/sketches/2026-09-18-names-lookup-flows.excalidraw +++ b/plans/sketches/2026-09-18-names-lookup-flows.excalidraw @@ -16,11 +16,11 @@ {"id":"te231930574466","type":"text","x":148.70184295573705,"y":446.294092339979,"width":32.26,"height":11.05,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"groupIds":["search"],"text":"Cancel","fontSize":9,"fontFamily":2,"index":"aP","strokeWidth":2,"lineHeight":1.25}, {"id":"te146535658481","type":"text","x":-17.678157044262946,"y":511.844092339979,"width":115.33,"height":16.25,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["search"],"text":"1a. From search","fontSize":13,"fontFamily":2,"index":"aW","strokeWidth":2,"lineHeight":1.25}, {"id":"te9023925185","type":"text","x":-17.678157044262946,"y":528.484092339979,"width":271.44,"height":68.75,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["search"],"text":"Typing filters to the chats found by this name,\nand Connect to resolves it, both as today. A name\nyou have a chat for is resolved at most once a\nday; any other name on every tap, so an owner\ncan check a change.","fontSize":11,"fontFamily":2,"index":"aX","strokeWidth":2,"lineHeight":1.25}, - {"id":"re137734344677","type":"rectangle","x":338.28393341265075,"y":302.0538844800932,"width":150.8570599529494,"height":107.10851256659409,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"index":"b03","strokeWidth":2,"boundElements":[{"id":"ar625193548486","type":"arrow"},{"id":"ar909442594990","type":"arrow"}]}, - {"id":"te719661998578","type":"text","x":380.9107231737149,"y":315.34091014517986,"width":65.60348043082108,"height":11.942850579608494,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"Name expired","fontSize":9.670324355958295,"fontFamily":2,"index":"b04","strokeWidth":2,"lineHeight":1.25}, - {"id":"te79947822704","type":"text","x":347.76085128148986,"y":334.08199874702706,"width":133.25706962510532,"height":18.857132494118677,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"bakery.simplex expired on 24 June\n2027. Its owner can renew it until 22","fontSize":7.736259484766636,"fontFamily":2,"index":"b05","strokeWidth":2,"lineHeight":1.25,"boundElements":[{"id":"ar909442594990","type":"arrow"}]}, - {"id":"te73085632245","type":"text","x":381.73270074397135,"y":355.20198714043994,"width":63.96919561466413,"height":9.428566247059338,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"September 2027.","fontSize":7.736259484766636,"fontFamily":2,"index":"b07","strokeWidth":2,"lineHeight":1.25}, - {"id":"te272497230298","type":"text","x":406.9529066643106,"y":384.46438864156977,"width":13.528783773985655,"height":10.057137330196626,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"OK","fontSize":7.736259484766636,"fontFamily":2,"index":"b08","strokeWidth":2,"lineHeight":1.25}, + {"id":"re137734344677","type":"rectangle","x":316.13016684172646,"y":300.7900393691602,"width":175.26957589025088,"height":121.93603568633762,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"index":"b03","strokeWidth":2,"boundElements":[{"id":"ar625193548486","type":"arrow"},{"id":"ar909442594990","type":"arrow"}]}, + {"id":"te719661998578","type":"text","x":368.18665936239535,"y":317.09831096098713,"width":74.68527140629419,"height":13.596154213852198,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"Name expired","fontSize":11.009031752107044,"fontFamily":2,"index":"b04","strokeWidth":2,"lineHeight":1.25}, + {"id":"te79947822704","type":"text","x":330.4476985161724,"y":338.4338144965705,"width":151.70445754403508,"height":21.467611916608732,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"bakery.simplex expired on 24 June\n2027. Its owner can renew it until 22","fontSize":8.807225401685637,"fontFamily":2,"index":"b05","strokeWidth":2,"lineHeight":1.25,"boundElements":[{"id":"ar909442594990","type":"arrow"},{"id":"ar625193548486","type":"arrow"}]}, + {"id":"te73085632245","type":"text","x":369.1224270613244,"y":362.4775398431723,"width":72.8247450401881,"height":10.733805958304366,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"September 2027.","fontSize":8.807225401685635,"fontFamily":2,"index":"b07","strokeWidth":2,"lineHeight":1.25}, + {"id":"te272497230298","type":"text","x":397.8339818708196,"y":395.79086992504824,"width":15.401635421197755,"height":11.449393022191323,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"groupIds":["over","over-alert"],"text":"OK","fontSize":8.807225401685635,"fontFamily":2,"index":"b08","strokeWidth":2,"lineHeight":1.25}, {"id":"te309994649535","type":"text","x":306.2232070690634,"y":518.14,"width":236.12,"height":16.25,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over"],"text":"1c. One alert over the found chats","fontSize":13,"fontFamily":2,"index":"b09","strokeWidth":2,"lineHeight":1.25}, {"id":"te1071904305641","type":"text","x":306.2232070690634,"y":534.78,"width":268.15,"height":41.25,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["over"],"text":"An actionable answer shows as one alert over the\nfiltered list. Dismissing it keeps the filter, so the\nchats found stay reachable.","fontSize":11,"fontFamily":2,"index":"b0A","strokeWidth":2,"lineHeight":1.25}, {"id":"re649142848260","type":"rectangle","x":-19.776792930936608,"y":690,"width":209.04,"height":223.6,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"groupIds":["message"],"index":"b0E","strokeWidth":2,"boundElements":[{"id":"ar507222825509","type":"arrow"},{"id":"ar11920695389","type":"arrow"}]}, @@ -215,11 +215,11 @@ {"id":"te166687152361","type":"text","x":780,"y":1645.16,"width":197.96,"height":15,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"text":"your address or channel claims it","fontSize":12,"fontFamily":2,"index":"b49","strokeWidth":2,"lineHeight":1.25}, {"id":"te921930439761","type":"text","x":60,"y":2112.98,"width":1923.76,"height":27.5,"strokeColor":"#1e1e1e","fillStyle":"solid","roughness":0,"text":"Tags compare with today's app: today, changed, new. The two tinted bands split the answers by whether a chat for the name is on the device. Greyed: needs the wallet (simplex-chat#7475), which has not landed. Design document, not screenshots; alerts are drawn in the app's iOS idiom. A lookup returns the chats found on the device and, on Connect to or a\ntapped name, one resolution from the registry: always for a name with no local chat, and for a name with one only when its cached answer is older than a day or past its expiry; registration will always resolve. The API is in simplex-name-lookup-alert-diff.md. Registering, owning, recovering and giving names are separate flows.","fontSize":11,"fontFamily":2,"index":"b4A","strokeWidth":2,"lineHeight":1.25}, {"id":"te719096292165","type":"text","x":357.85,"y":362.4,"width":53.20068359375,"height":13.75,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"text":"Connect to","fontSize":11,"fontFamily":2,"textAlign":"center","verticalAlign":"middle","containerId":"ar909442594990","index":"b4C","strokeWidth":2,"lineHeight":1.25}, - {"id":"ar909442594990","type":"arrow","x":196.36184295573696,"y":364.00409233997897,"width":134.02710699216095,"height":0.3171881280713933,"strokeColor":"#007aff","fillStyle":"solid","strokeWidth":2,"roughness":0,"boundElements":[{"id":"te719096292165","type":"text"}],"points":[[0,0],[134.02710699216095,0.3171881280713933]],"endArrowhead":"arrow","elbowed":true,"fixedSegments":null,"index":"b4D","startBinding":{"elementId":"re698762997543","fixedPoint":[1.023918867202449,0.49964387464387444],"focus":0,"gap":0},"endBinding":{"elementId":"re137734344677","fixedPoint":[-0.05233419945486937,0.5813487135230525],"focus":0,"gap":0}}, + {"id":"ar909442594990","type":"arrow","x":196.361842955737,"y":364.00409233997897,"width":114.66821606441519,"height":2.359878586740649,"strokeColor":"#007aff","fillStyle":"solid","strokeWidth":2,"roughness":0,"boundElements":[{"id":"te719096292165","type":"text"}],"points":[[0,0],[57.38416194299478,0],[57.38416194299478,-2.359878586740649],[114.66821606441519,-2.359878586740649]],"endArrowhead":"arrow","elbowed":true,"fixedSegments":null,"index":"b4D","startBinding":{"elementId":"re698762997543","fixedPoint":[1.023918867202449,0.49964387464387444],"focus":0,"gap":0},"endBinding":{"elementId":"re137734344677","fixedPoint":[-0.029098648728218802,0.499066367391314],"focus":0,"gap":1}}, {"id":"te597337641794","type":"text","x":300.05,"y":791,"width":56.87451171875,"height":13.75,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"text":"chats found","fontSize":11,"fontFamily":2,"textAlign":"center","verticalAlign":"middle","containerId":"ar507222825509","index":"b4E","strokeWidth":2,"lineHeight":1.25}, {"id":"ar507222825509","type":"arrow","x":199.2231269090634,"y":813.7999412,"width":123.3252558673932,"height":1.3543268028437296,"strokeColor":"#007aff","fillStyle":"solid","strokeWidth":2,"roughness":0,"boundElements":[{"id":"te597337641794","type":"text"}],"points":[[0,0],[56.00008015999998,0],[56.00008015999998,-1.3543268028437296],[123.3252558673932,-1.3543268028437296]],"endArrowhead":"arrow","elbowed":true,"fixedSegments":[{"index":2,"start":[56.00008015999998,0],"end":[56.00008015999998,-1.3543268028437296]}],"endBinding":{"elementId":"re984646293193","focus":0,"gap":10,"fixedPoint":[-0.047621,0.499684]},"index":"b4F","startBinding":{"elementId":"re649142848260","focus":0,"gap":10,"fixedPoint":[1.047646,0.553667]}}, {"id":"te509749597660","type":"text","x":698.55,"y":362.4,"width":53.8076171875,"height":13.75,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"text":"the answer","fontSize":11,"fontFamily":2,"textAlign":"center","verticalAlign":"middle","containerId":"ar625193548486","index":"b4G","strokeWidth":2,"lineHeight":1.25}, - {"id":"ar625193548486","type":"arrow","x":486.0179209065952,"y":352.93913124114573,"width":152.3484204462074,"height":0.4306126831448296,"strokeColor":"#007aff","fillStyle":"solid","strokeWidth":2,"roughness":0,"boundElements":[{"id":"te509749597660","type":"text"}],"points":[[0,0],[152.3484204462074,0.4306126831448296]],"endArrowhead":"arrow","elbowed":true,"fixedSegments":null,"index":"b4H","startBinding":{"elementId":"re137734344677","focus":0,"gap":5,"fixedPoint":[1.032051,0.499097]},"endBinding":{"elementId":"i9ADag2YXvSUyLm-vKlDH","fixedPoint":[-26.187111437291986,0.07311428340218563],"focus":0,"gap":1}}, + {"id":"ar625193548486","type":"arrow","x":498.6622717253598,"y":391.2664916007193,"width":137.97755227766993,"height":0.09643745591267816,"strokeColor":"#007aff","fillStyle":"solid","strokeWidth":2,"roughness":0,"boundElements":[{"id":"te509749597660","type":"text"}],"points":[[0,0],[137.97755227766993,0.09643745591267816]],"endArrowhead":"arrow","elbowed":true,"fixedSegments":null,"index":"b4H","startBinding":{"elementId":"re137734344677","fixedPoint":[1.0414363357501935,0.7419992926807656],"focus":0,"gap":0},"endBinding":{"elementId":"i9ADag2YXvSUyLm-vKlDH","fixedPoint":[-26.187111437291986,0.07311428340218563],"focus":0,"gap":1}}, {"id":"te1001643326240","type":"text","x":306.75,"y":997.1,"width":83.76220703125,"height":13.75,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"text":"the same answer","fontSize":11,"fontFamily":2,"textAlign":"center","verticalAlign":"middle","containerId":"ar11920695389","index":"b4I","strokeWidth":2,"lineHeight":1.25}, {"id":"ar11920695389","type":"arrow","x":199.2231269090634,"y":889.599894,"width":432.13147298422797,"height":147.83927408611544,"strokeColor":"#007aff","fillStyle":"solid","strokeWidth":2,"roughness":0,"boundElements":[{"id":"te1001643326240","type":"text"}],"points":[[0,0],[60.12854667662532,0],[60.12854667662532,147.83927408611544],[432.13147298422797,147.83927408611544]],"endArrowhead":"arrow","elbowed":true,"fixedSegments":[{"index":2,"start":[60.12854667662532,0],"end":[60.12854667662532,147.83927408611544]}],"index":"b4J","startBinding":{"elementId":"re649142848260","focus":0,"gap":10,"fixedPoint":[1.047646,0.892665]},"endBinding":{"elementId":"i9ADag2YXvSUyLm-vKlDH","fixedPoint":[-54.06464610022175,0.44492907483548705],"focus":0,"gap":0},"startIsSpecial":false,"endIsSpecial":false}, {"id":"te723091252074","type":"text","x":846.55,"y":296,"width":66.0322265625,"height":13.75,"strokeColor":"#007aff","fillStyle":"solid","roughness":0,"text":"new resource","fontSize":11,"fontFamily":2,"textAlign":"center","verticalAlign":"middle","containerId":"ar967582753031","index":"b4M","strokeWidth":2,"lineHeight":1.25}, diff --git a/plans/sketches/normalize-excalidraw.py b/plans/sketches/normalize-excalidraw.py new file mode 100644 index 0000000000..ed3ea87a3c --- /dev/null +++ b/plans/sketches/normalize-excalidraw.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +"""Shrink an .excalidraw file without changing what it renders. + +Excalidraw writes every element with its full schema, pretty-printed, so a +354-element sketch costs 14k lines and ~90k tokens to read, and a one-field +edit shows up as a 27-line diff block. This drops the fields Excalidraw +re-derives on load and puts one element per line, so a changed element is +one changed line. + +Kept on purpose: roughness, fillStyle, fontFamily and strokeColor, which +this drawing sets away from the Excalidraw default; strokeWidth, whose +default is 2, so dropping a 1 doubles the stroke; and lineHeight, which +Excalidraw recomputes from font metrics when it is absent. + +Usage: normalize-excalidraw.py FILE... rewrites each file in place + +Runs from .git/hooks/pre-commit on every staged .excalidraw file. +""" +import json +import sys + +# Rewritten by Excalidraw on every save; carries no meaning for the drawing. +DROP = {"version", "versionNonce", "updated", "seed", "baseline"} + +# Dropped only where the element already holds the default value. +DEFAULT = { + "isDeleted": False, "locked": False, "link": None, "frameId": None, + "angle": 0, "opacity": 100, "strokeStyle": "solid", + "groupIds": [], "boundElements": [], "roundness": None, + "backgroundColor": "transparent", "autoResize": True, + "textAlign": "left", "verticalAlign": "top", "containerId": None, + "startBinding": None, "endBinding": None, "startArrowhead": None, + "endArrowhead": None, "lastCommittedPoint": None, + "startIsSpecial": None, "endIsSpecial": None, +} + + +def lean(element): + return {k: v for k, v in element.items() + if k not in DROP + and not (k in DEFAULT and v == DEFAULT[k]) + and not (k == "originalText" and v == element.get("text"))} + + +def dump(scene): + head = [f"{json.dumps(k)}: {json.dumps(v)}" for k, v in scene.items() + if k not in ("elements", "appState", "files")] + body = ",\n".join(" " + json.dumps(lean(e), separators=(",", ":")) + for e in scene["elements"]) + tail = [f"{json.dumps(k)}: {json.dumps(scene[k])}" + for k in ("appState", "files") if k in scene] + return ("{\n " + ",\n ".join(head) + ',\n "elements": [\n' + body + + "\n ],\n " + ",\n ".join(tail) + "\n}\n") + + +def main(paths): + for path in paths: + with open(path) as f: + before = f.read() + text = dump(json.loads(before)) + with open(path, "w") as f: + f.write(text) + print(f"{path}: {len(before)} -> {len(text)} bytes, " + f"{before.count(chr(10))} -> {text.count(chr(10))} lines") + + +if __name__ == "__main__": + if len(sys.argv) < 2: + sys.exit(__doc__) + main(sys.argv[1:])