Repository navigation
OpenConstructionERP v16.8.2
The first person to open the application after a restart used to wait about a minute for the front page. FastAPI builds a route's dependency tree, its response model and its operation id the first time a request reaches route matching, for every route the router holds, at once and on the event loop. With 190 module routers mounted that measured 32.1 seconds against 0.04 for the second request, and the bill fell on whichever request arrived first rather than on an expensive one: the front page is a static file that touches no database and it paid the same 32 seconds a health check did, with everything issued alongside it queued behind. A health check sent at that moment answered 33.9 seconds later, five times the 12 second deadline the desktop launcher allows each poll, which is how a splash screen came to sit in front of a backend that was alive, listening and reporting itself unreachable. The work is not avoidable, so it happens during startup now, before the listening socket exists, where a client either cannot connect yet, which every launcher already handles and the boot log narrates a step at a time, or connects to a process that answers. The warm up is a synthetic request aimed at a path that matches nothing on purpose, because matching stops at the first route that matches and replaying a plausible request would have left every later router cold, which is the same defect moved somewhere harder to find. The API reference document is no longer built during startup either. It takes 73 seconds once the route table is warm and 134 on a first run, for 2923 paths, and its only readers are the three reference endpoints, none of which is on the startup path, so every launch spent a minute of a core filling a cache that most installations never read. Whoever opens one of those pages pays for it there instead, cached and locked exactly as before, and an operator serving them to other people can have the old behaviour back with OE_PRIME_OPENAPI_SCHEMA. Measured on one Windows machine with a warm data directory and no demo seed, the port used to accept at 110.6 seconds and the first page took 58.7 of the 169.8 seconds it took to arrive, with the process staying slow until 229. The port now accepts at 143.6 seconds with the route table already built, and the first page arrives in single digit seconds. Two runs of that last measurement disagree with each other and agree on the order of magnitude, so the order of magnitude is what is claimed here rather than a decimal only one of them supports.
The project list drew the wrong kind of map. Every card painted one static tile of shaded relief at zoom 6, which is as deep as that source goes and carries no streets at any zoom, so the picture was geographically correct and of no use to anybody: a person opening a project list wants roads, street names and building footprints, and got landform. Nothing caught it because it looked like a map, the request answered 200 and the image decoded. Cards now render the vendored vector style through an offscreen map, read the canvas once and destroy the context, rather than holding a live one open for each of the dozen or so cards a grid shows at once, and the renders are serialised so a full grid still only ever has one context alive. Measured through a real graphics context rather than assumed: at zoom 15 six European cities came back with 95 to 235 road segments and 12 to 46 building footprints apiece, against zero buildings and zero street names at the zoom that was retired. Deliberately no tighter than that, because a card's coordinates often come from geocoding a city and a country, which answers with a centroid, and framing an arbitrary downtown block would hide from the reader that the pin is approximate. The relief tile stays on as the fallback, shown until the render arrives and kept if it never does, so a browser without WebGL still gets a picture instead of an empty box.
The map on the project detail page had been two pixels tall. The shell went out carrying two height utilities at once, its own default and the one the page asked for, and appending the caller's class after the default does not decide which one applies: both names reach the emitted stylesheet and the one written last there wins, whatever order they sit in on the element. The page asked for 32rem, got a full height against an auto height parent, computed to 2 pixels, and the shell's own clipping cropped a live 300 pixel map canvas down to a hairline. Nothing could catch that except looking, because the map mounted, the canvas painted at the right size, the attribution was correct, no request failed and the console was clean. Two comments in the file asserted the opposite of the truth and are the reason it lasted, and both are corrected rather than deleted, because the next reader would otherwise derive the same wrong conclusion from them again. The default stands down now when the caller states a height of its own. A minimum height does not count as one, so the layout that pairs the two keeps working, and a height that only applies above a breakpoint does not count either, because treating it as a stated height would leave the element with no height at all below that breakpoint.
A bill position answered a different norm depending on which endpoint you asked. The production norm behind a price shipped in 16.8.1 with the write side done and the headline read side not, because a position was turned into a response by two separate builders and only one of them knew about the new fields. Reading the whole bill returned the provenance, reading the single position returned nothing, with the value sitting in the column the whole time. The two builders had drifted on six fields in both directions, so the whole bill read had also been reporting the concurrency token as zero on every row and the two of them disagreed about a confidence figure. There is one builder now, and adding a field to it reaches every endpoint that returns a position. A row priced before the column existed still reads back empty and is repaired by being copied or by the backfill in the migration, which is the behaviour that was asked for.
The API reference page fetched itself from a content delivery network. It served the stock page, which pulls its script from one external host, its fonts from another and its icon from a third, so on an install with no route out to the internet the shell arrived, nothing upgraded it, and the reader got a white page with nothing in the console to search for. Self-hosting is the premise of this platform rather than an edge case. The renderer now ships in the product at a pinned version, one route serves the assets of both API pages, the fonts are dropped rather than vendored so headings fall back to the system typeface, and the icon points at the install. It costs less on the wire than the assets already there and adds no dependency. Verified by rendering the page with every off-host request refused, and the one image request that still leaves the machine is written down in the test and the README rather than left to be found in a network log.
A status check that answers without credentials could still launch four converter binaries. The converter listing takes a flag that runs a smoke test on every installed converter, and a smoke test is not a file stat: it starts the executable and waits up to eight seconds for it, four of those on a full install, for a caller who never signed in. The route next door asks for a permission for exactly that reason, and this one said in its own documentation that it stays open because it only looks at files, which was true of the default call and not of that one. The listing still answers everybody and no sign-in requirement was added. A caller without a usable token is declined the smoke test rather than refused the request, so the answer comes back in the shape every reader already handles, and all three callers that set the flag are things an operator does while signed in.
Two build faults that had been invisible because git agreed with itself. Forty three ignore rules meant for the backend sat in the wrong file and were inert for the wheel build, so an internal defect log, two seeding scripts and 495 entries of a stale frontend bundle rode into a locally built archive, about eleven percent of it. Published releases build on clean runners and carry none of it, so the exposure was a hand made upload, and the source archive had no exclusion list protecting it at all. The rules move to where the build reads them, the wheel drops from 4698 entries to 4200 and from 96.6 to 85.8 MiB, and the tree guard gained the structural question instead of four more names: does the archive hold a file that git does not track.
And 16.8.1 shipped with no desktop installers at all, which is fixed here in the machinery rather than in the product. The check that opens the built application ran all three of its legs against one port while the Windows helper process survives every stop signal available to it, so one process that would not die failed the whole job and took the four installers with it. Each leg now takes its own port instead of queueing behind the last one, all three report the same workspace so the restart leg is still proving what it was written to prove, and the command that was supposed to kill the helper no longer discards the message where it says it could not.
The cost base browser answered an error to the address its own documentation gives, and showed a spinner for ever when it did. Routes match in registration order, and the costs router declared an unconstrained single segment catch all about 1300 lines above the literal base catalogue route, so the literal path was read as a cost item identifier, the parse failed, and the path sanitiser collapsed that into an opaque bad request which named nothing wrong because nothing was. The item routes take an identifier shape now, so the literal routes win and a genuinely unknown single segment path under that prefix answers not found rather than bad request. The trailing slash form had been reaching the handler all along, which is why the screens worked while the documented form did not. The other half is what a reader saw when a request did fail: that query does not retry, so a failure leaves the data undefined for good, and the import page, the database setup page and the onboarding wizard all read only the data and treated undefined as still loading. One frame meant both wait a moment and this is never arriving, with nothing on screen to say which, or that there was anything to be done about it. There is an error panel now that names what failed, quotes the reason the server gave, says the list comes from the server and offers a retry.
Getting paid for a month of work is now written out in the words and the paperwork of eight more places: the boletim de medicao in Brazil, the estimacion invoiced as a CFDI in Mexico, the KS-2 act and the KS-3 certificate in Russia, the teljesitesigazolas in Hungary, the security of payment clock in Australia, a payment claim under the Construction Contracts Act in New Zealand, and the interim payment certificate in Saudi Arabia and South Africa. It is one case rather than eight, and that is the point of the shape: a single spine describes the job, each jurisdiction contributes a layer naming its own documents, deadlines and signatures, and a reader anywhere in that list meets the sequence they already follow rather than somebody else's with a note attached. The catalogue entries are translated into the four languages that carry the full catalogue, because a case added without them renders English to every reader of those languages and nothing else reports it, and a guard now fails the build rather than letting that ship. Uzbek is a smaller instance of the same thing: four cost strings had been written with a plain ASCII apostrophe, which is not the mark that language uses, and they are spelled properly again.
Full Changelog: v16.8.1...v16.8.2