← The journal

A database in your tab. The mockup stays on your computer.

From a .bmpr file to an SVG canvas: a tour of Telario’s local architecture, including SQLite, WebAssembly, memory, exports and practical limits.

A database and workbench contained inside a browser window.
Original editorial illustration for Telario. Generated with AI.

Opening an application in a browser does not tell you where its work happens. It might be the interface of a remote service, a static document with a few interactions, or a program executing most of its logic on your machine. To understand what happens to files, follow the data rather than the shape of the window.

In Telario, the selected document is read and processed in the browser. The server delivers the application: HTML, JavaScript, CSS, and the WebAssembly module used for SQLite. The editor does not send the project to a conversion service. That decision answers a concrete need: opening a working archive without turning it into an attachment destined for another server.

“Local” is not a complete explanation, however. Memory, dependencies, compatibility, downloads, and persistence still matter. A tab can execute a database and lose undownloaded changes when closed. A server on the same machine can deliver the app without knowing the document being opened. These distinctions become especially important when moving from a successful demo to a real project containing many pages and images. Understanding them makes the convenience useful rather than mysterious.

1. The server delivers tools, not document processing

In the Docker workflow, Telario is built and served through Nginx. The browser requests application files and executes them. There is no need to mount a mockup directory inside the container: document selection happens through the browser interface. Separating these stages avoids a common assumption that every file opened by a web page must necessarily pass through the server that delivered the page.

The first setup still needs a network connection to download Docker images and dependencies. After building, the editor does not need CDNs or external services to process documents. This describes the app’s intended path, not every process running on the operating system. Browser extensions, the synchronised folder containing your file, and other installed software have their own behaviour. A statement about the editor should not quietly become a claim about the whole machine.

The useful question is precise: which requests does the editor produce when a document is opened and edited? Browser tests also check for unexpected external requests along the tested paths. Observable behaviour is stronger evidence than the word “privacy” printed beside a button. Architecture should make the promise inspectable in code and use. It should also help distinguish an app request from activity produced by the surrounding environment when investigating an unexpected result.

2. A .bmpr contains more than an image

Telario treats a .bmpr as a SQLite archive. The reader looks for required tables, reads format information, and retrieves resources and pages. Page data describes controls: types, coordinates, dimensions, properties, and other information. What you see is a representation of those structures, not a PNG hidden beneath a different file extension.

This explains why successful opening requires more than renaming a file. The app must interpret structure, recognise elements, and choose how to draw them. A button has text and geometry; an arrow has endpoints and style; a group contains other controls. Some details are understood, while others are preserved without being completely rendered. Preservation and visualisation are different capabilities, and treating them as identical leads to misleading conclusions about both missing graphics and saved data.

The reader retains original bytes and raw JSON. That avoids reconstructing the entire archive solely from the subset understood by the interface. Unknown fields matter here: the absence of a command in the property inspector should not automatically delete existing information. The first rule of a partial interpreter is to avoid confusing “I do not use this” with “nobody needs this.” Keeping source information available creates a better basis for subsequent editing, inspection, and cautious writing than a model that silently discards everything outside its current vocabulary.

3. How SQLite lives in a browser

The app uses sql.js to initialise SQLite through a WebAssembly module distributed with the build. The code imports the .wasm asset location, supplies it during initialisation, and reuses the result. If initialisation fails, the cached promise is cleared so a later attempt can retry rather than keeping the whole session trapped in a failed state.

When a user selects a file, its bytes become the input to an in-memory database. Queries read tables and resources without a remote database connection. There is no separate SQLite server to install, no database user to create, and no company connection string to configure. The complexity remains within the program and the resources of the device running the browser. The interface may look like a website, but the document-processing work is happening on the client.

Convenience does not mean zero cost. File content, the editor’s model, and editing history all consume memory. A large document can demand more resources than its download size suggests. The opening limit is 100 MB, but that is an input check rather than a universal performance guarantee. Performance questions always need a file, an operation, and a device as context. Without those, “runs in the browser” says almost nothing about how a particular project will feel during actual use.

4. The model separates documents from gestures

After reading the archive, the model exposes pages and controls through functions for text, geometry, and properties. The interface does not need every SQLite query detail to move an element. The renderer does not decide how the final download is created. This separation makes the information path easier to inspect and helps place fixes at the appropriate level.

The repository makes these responsibilities visible. src/io handles archives; src/model handles data and geometry; src/render draws; src/editor manages history; src/export produces outputs; and src/ui implements controls and interactions. These are not six separate products. They are different responsibilities within a small application that should remain understandable when it changes. Directory names are useful when they reflect real ownership of behaviour rather than simply making the tree look organised.

A graphical fix should not alter the writer. An icon-alias correction should not rewrite persisted document identifiers. A new property-inspector action does need checking against the data it produces. Separation does not eliminate regressions, but it makes relevant tests easier to choose and indirect effects easier to reason about. The practical benefit appears when a change can be described precisely: which layer changes, which output should improve, and which document properties should stay the same. That is a stronger review than “the app still opens.”

5. SVG is the canvas language

The renderer draws controls using SVG. Geometry, text, and paths remain vector elements transformed relative to page coordinates. Zoom changes the view without requiring every control to be replaced by a fresh raster image. SVG export can reuse the representation system, preserving a readable relationship between what the app displays and what it produces outside the editor.

The application does not use Balsamiq’s proprietary graphical assets to reproduce every control exactly. Fonts come from the system, and several shapes have an independent rendering. rough.js provides an optional sketch appearance. That hand-drawn effect is a visual mode, not certification that the result matches the original product. The difference matters whenever a review depends on typography, alignment, or a specific visual convention.

Vector rendering also makes it possible to embed icons as paths instead of depending on a remote icon font. It does not solve every problem automatically. Rich text, tables, groups, and curved arrows may still require approximations. The advantage is the ability to inspect drawing rules element by element. The limitation is having to implement those rules rather than assuming that enough data in an archive makes every rendering decision obvious. A useful renderer needs both geometry and an honest description of what its interpretation does not yet cover.

6. Images are not all equivalent resources

Supported embedded resources include PNG, JPEG, GIF, and WebP. The reader recognises expected raster signatures and builds data the browser can use. External resources, links, and embedded SVG are not loaded or executed. This is deliberate: opening an archive should not automatically execute active content or retrieve addresses found inside the document.

That behaviour may differ from the author’s environment. An externally linked image could be available there and absent from the local interpretation. The correct response is to identify the resource type and the boundary, rather than promise that every missing picture can be recovered without additional conditions. A missing visual does not necessarily imply broken database reading, and not every visual difference has the same cause.

A useful report often needs only to establish whether the resource was embedded, which format it used, and which control referenced it. Publishing a customer photograph is unnecessary to demonstrate a decoding issue when a synthetic image in the same format can isolate the behaviour. Preservation and display should again be evaluated separately: an unrendered resource is not automatically a deleted resource. The distinction guides both investigation and communication, preventing a visual symptom from being reported as data loss before the archive has actually been examined.

7. History is working memory

Undo and redo allow exploration without treating every gesture as final. Telario maintains history for up to one hundred operations. That is useful within the session, but it is not a permanent revision archive. Closing or reloading the tab can lose work that has not been downloaded, even if the undo button was available a moment earlier.

This affects user expectations. Many web applications encourage the assumption that every change has already been saved somewhere. In a local editor without persistent autosave, that assumption is wrong. Results must be deliberately exported and kept in a recognisable location. A page continuing to display a change is not evidence that a durable copy exists. History, visible state, and saved output answer different questions and should not be treated as interchangeable proof of preservation.

For a long trial, produce intermediate outputs appropriate to the task: an image, JSON, or an experimentally saved copy that has been checked. The right format depends on the next step. Saving everything indiscriminately can create confusion; saving nothing relies too heavily on a tab surviving. Between those extremes lies a conscious choice. A small routine of checking the downloaded file before ending a session is often more valuable than assuming that a familiar browser interface necessarily provides familiar cloud-document recovery features.

8. Six exports answer six different questions

SVG and PNG describe a page as an image. Markdown extracts text from all pages. JSON exposes data for inspection. Mermaid and draw.io attempt to reconstruct a diagram structure, inferring connections from endpoint proximity. These are not equivalent outputs and should not be presented as six interchangeable ways to preserve the complete project.

Choose an image when the task is showing a screen, then inspect its rendering. Start with Markdown when reusing text, then restore the relevant context. Use JSON when examining data. For a diagram editable elsewhere, review inferred relationships node by node. An arrow close to two shapes does not necessarily contain the same semantics as an explicitly modelled connection in another application. Convenience in generating an output cannot remove the need to validate the meaning of that transformation.

PNG output is constrained to 8,192 pixels per side and approximately thirty-two megapixels. Very large pages may therefore be reduced. Before using an image in a handoff, check text readability, framing, and completeness. Successful export begins the inspection of the result; it does not finish it. A correctly downloaded file can still be unsuitable for its intended purpose. The useful test is opening the actual output in the environment where the recipient will use it and verifying the details that matter to the decision.

9. Ten experiments for understanding the architecture

Follow the page’s network requests

Open browser developer tools and observe requests while the app loads, then select a test document. Distinguish requests for the editor’s tools from any transmission of project content. Do not automatically publish screenshots of developer tools: URLs, paths, and unrelated details may expose information that is unnecessary for the investigation.

Begin with the demo and a recognisably named synthetic file. Note what happens when changing pages and exporting. If an unexpected request appears, determine whether it came from app code, a browser extension, or surrounding context. Repeat with a clean environment where possible. The objective is not proving an absolute word such as “secure.” It is checking a concrete behaviour: the editor does not upload the document to a remote service for processing. That narrower question can receive an evidence-based answer. It also helps explain precisely what was observed if another user’s environment produces a different network trace.

Open an invalid archive

A .bmpr extension does not automatically make a file a supported database. Testing invalid input checks how the app communicates failure and whether it preserves the currently open project. An input error should not quietly become a loss of existing working context. The recipient needs to tell a failed opening apart from an empty project.

Use synthetic data and observe the message; there is no reason to rename an important document for this test. After the error, return to the previous page and check that it remains available. This tests the interface and reader together: a failed attempt should be recognisable without replacing the existing project. Error quality is part of a local application’s usability. Without a remote operator to explain the result, the program should at least distinguish failure from success and leave enough context to continue safely. A clear error can turn an unfamiliar file into a reportable compatibility question rather than a frightening suspicion of lost work.

Compare the demo’s pages

The synthetic demo is useful because it contains no company data and can be reproduced. Moving between its pages helps explain the relationship between the page list, selection, and canvas. Try zooming and observe whether you are changing the view or document geometry. Those operations are different, and learning the distinction makes later work easier.

This familiarity prevents confusion during a real-project trial. A missing element could be outside the view, on another page, or represented differently than expected. Fitting the page and checking its name are simple steps before concluding that content disappeared. The demo cannot prove compatibility with every archive, but it provides stable ground for learning gestures. It is the right place to make a mistaken click without turning the mistake into a question about a customer project. Once the interface is familiar, an unexpected real-file result becomes easier to separate from an unfamiliar command or an accidental view change.

Change text, undo, and redo

Select a demo label, change its content, and undo the change. Then redo it. Observe the text both on the canvas and in the property inspector. This checks whether the interface, model, and history describe the same state rather than each updating independently. A small recognisable change is easier to assess than a large collection of edits.

Later, try a sequence involving movement and resizing, but keep a question for each experiment. Which relationship are you checking? A useful manual test produces a specific observation. Clicking many controls until something looks odd can reveal a problem, but it often creates a long reconstruction task. Even exploratory work benefits from a little discipline when data lives only within the session. Record the shortest sequence that produces the discrepancy. That sequence is usually more valuable to investigation than a lengthy description of everything attempted before noticing the final symptom on the screen.

Export one element as SVG and PNG

The two outputs let you compare vector representation with rasterisation. Open both outside the editor and inspect edges, text, images, and background. Confirm that the intended page was exported and that its filename does not create confusion with an older result. A successful download alone establishes very little about final image quality.

If a difference appears, identify where it begins: only in PNG, in SVG too, or already on the canvas. That helps locate the problem between shared rendering and rasterisation. Do not modify the document before understanding which level is involved. Text clipped only in the raster output calls for a different investigation from text already interpreted incorrectly in the model. Comparing multiple outputs from the same data exposes where a transformation introduced a difference. It is a simple diagnostic technique that often provides more information than repeatedly exporting the same page and hoping a new filename will improve the result.

Try a synthetic raster resource

An example image can test a format without exposing real content. Compare a supported embedded resource with an external reference, remembering that the latter is not loaded by the editor. Define the expected difference first; otherwise, an intentional boundary may be mistaken for random behaviour. Expected results are part of designing a useful experiment.

Observe the resource in SVG and PNG exports as well as the canvas. Evaluate the complete workflow you actually intend to use. If the image is visible in the editor but absent from an output, that differs from an initial reading failure. Preserve that distinction in the report. A small project containing one control and a synthetic image can be more useful than an archive full of unrelated pages. Reducing the example does not minimise the importance of the issue. It removes distractions so the relevant cause can be investigated without accessing business content or reconstructing a much larger document.

Measure an operation, not an impression

Choose an observable event when evaluating performance: opening, page switching, dragging, or exporting. A general feeling of slowness mixes activities with very different costs. Note approximate document size, pages involved, and image presence without including private details in a public report. Those observations turn an impression into something another person can investigate.

Repeat the same operation under similar conditions and treat first loading separately from subsequent actions. Initialising WebAssembly is not the same event as every page change. Avoid turning one measurement into a promise for all computers. The objective is identifying a concrete constraint rather than building an improvised benchmark league. “PNG export on this page takes longer than switching pages” is modest but informative. It can guide a fix or simply help choose the right output format. Separating input loading, rendering, interaction, and export also prevents an improvement in one area from concealing an unresolved delay in another part of the workflow.

Reload only after exporting

This experiment makes the difference between memory and persistence explicit. With synthetic data, modify an element, export the result you need, and reload. The app does not provide persistent project autosave. If you expected work to return automatically, you have discovered an expectation worth correcting before an important session. The demonstration should be educational rather than costly.

Do not use a real document to prove that undownloaded changes can disappear. The demo is enough. Instead, confirm that the downloaded file exists and can be opened, because that is the durable result of your action. Tab memory is useful for working; it is not a hidden folder guaranteeing recovery. Naming the distinction supports a simple habit: before closing, verify what has actually been preserved outside the app. The check takes little time and prevents an avoidable surprise. It also helps recipients understand why the presence of undo history does not mean a project has already been saved somewhere permanent.

Read JSON without mistaking it for a specification

Data inspection can help explain an unknown control or a property the renderer does not handle. Seeing a field in JSON does not automatically reveal all its rules. It may depend on a version, control type, or context requiring further investigation. Raw visibility is a useful starting point, not the same thing as complete format knowledge.

Use the data to form observable hypotheses: which identifier is read, which coordinates exist, and which property distinguishes two cases? Then compare behaviour. Avoid publishing an entire private project’s JSON to ask about one icon. A reduced structure with synthetic values is often sufficient. Inspection is diagnostic, not an invitation to treat every observed field as official documentation. Good analysis separates what was observed, what was inferred, and what remains unverified. Maintaining those categories makes later corrections easier because a hypothesis can be revised without pretending that the original observation was false or that a partial understanding was ever a universal rule.

Compare website claims with code and behaviour

A presentation page can age faster than software. When a feature matters, compare its description with the README, documented limits, and the version being used. The website should make that comparison easy by linking project references rather than asking users to trust its tone. Documentation is part of the interface between the project and its users.

A mismatch between copy and build behaviour deserves correction even if the code is functioning as designed. For Telario, the important boundaries remain explicit: a first working version, approximate rendering for various controls, and experimental .bmpr saving. An interesting architecture does not make those notes less necessary. The easier it becomes to run a successful demo, the more useful it is to explain what that success establishes and which checks it does not replace. Accurate language helps a user adopt the app for a suitable task without unintentionally turning a narrow demonstration into evidence for a much wider promise.

10. What changes when a file gets large

An in-memory architecture moves work onto the user’s device. Embedded images, control counts, and editing history can increase resource consumption. Two equally sized archives are not necessarily equivalent: one may hold a few heavy images, another many pages of geometry and text. Observe the operations relevant to your own workflow instead of relying on file size as a complete complexity measure.

The 100 MB limit prevents some excessive openings, but it is not a complete performance forecast. Choose a representative archive for an initial trial and preserve the original material. Check essential pages first, then required exports. You do not need to exercise every feature simply because the file opened. A proportionate trial gives better evidence than a long random session.

When delays appear, describe the point in the process: reading, model construction, page switching, drawing, or output generation. Users may not be able to isolate the exact implementation layer, but they can usually identify the gesture preceding the wait. That information lets development begin in the right place rather than searching vaguely “in the browser.” The browser is the room. Finding the problem also requires knowing which tool was being used inside it.

11. A readable scope is a feature

A small app does not need to pretend to be a complete platform. Telario does not manage users, provide online collaboration, or synchronise projects. The server does not receive mockups or act as a shared document archive. These boundaries reduce some complexity and leave other responsibilities with the people organising work: preservation, revisions, machine access, and handoff validation.

Local processing offers a concrete benefit when it fits the problem. I can open a document without creating a conversion service or turning a small personal need into file-management infrastructure. The trade-off is accepting session limits and partial interpretation. This is not a universal formula. It is a coherent choice for a particular activity, and that coherence is more useful than a fashionable architectural label.

Try the setup guide, compare features with compatibility and limits, and inspect the app source to follow the path from bytes to canvas. For me, the interesting part is not simply a database inside a tab. It is that the tab can finally show the document and let me return to work while explaining what it is not doing. That combination of utility and clear boundaries is the real architectural objective.

12. Updating the app without confusing tools and documents

A new build changes the tools used to read a project. It does not update previously downloaded files, repair older images, or recover edits left in another tab’s memory. Preserve required results before refreshing the page or rebuilding during a session. Separating software from documents helps only if everyday operations respect that separation too.

Compare two app versions using the same input archive and page. Changing software, file, and procedure simultaneously makes differences difficult to attribute. Record the build version or commit when checking a regression. This is unnecessary for every informal consultation, but important when a comparison guides a renderer or writer correction. A small amount of provenance makes an observation reproducible.

Check what should remain unchanged as well as what should improve. An icon update can improve display without altering stored identifiers. Interface CSS should not change document geometry. A clearly bounded expected effect helps select meaningful checks and detect side effects. Responsibilities become practical quality criteria rather than abstract design vocabulary. That is how a small codebase can support careful change without pretending that any test suite proves the absence of every possible regression.

13. Local dependencies should not become invisible dependencies

The app bundles libraries with distinct jobs: React for the interface, sql.js for SQLite, rough.js for sketch rendering, and Font Awesome Free for the icon catalog. Serving these locally does not remove their authors or licences. Attribution belongs in the distributed package and should follow dependency updates. The project’s own licensing decision is separate from those existing component licences.

Provenance and versioning also have technical value. If an icon changes name or a component changes behaviour, knowing the included release makes comparison less ambiguous. Reproducibility concerns external components as well as project code. An understandable build can explain where its tools came from and why a particular output was produced.

The icon catalog therefore comes from a specific official release and travels with the app. Drawing a control does not require requesting its path from a remote service. Avoiding that request improves independence after loading, while increasing the initial build size. This is a concrete trade-off between distributed assets and autonomy during use, best evaluated through measurements rather than an abstract preference for ever smaller files.

These choices may evolve. What should remain stable is how they are described: which components are included, what work they perform, which requests they make, and which data crosses the machine boundary. Answer those questions and “local” stops being a reassuring adjective. It becomes an observable property of the data’s actual route through the application.