\\n\\n<Line\\n // Two lines\\n positions={[\\n [[300, 250], [350, 350], [400, 250], [450, 350]],\\n [[300, 350], [350, 450], [400, 350], [450, 450]],\\n ]}\\n // With color per line\\n color={[\'#ffa040\', \'#7f40a0\']}\\n // And width per vertex\\n widths={[[1, 2, 2, 1], [1, 2, 2, 1]}\\n/>\\n\\n
\\n\\n\\nThis involves a comprehensive buffer interleaving and copying mechanism, that has to satisfy all the alignment constraints. This then leverages @use-gpu/shader
\'s structType(…)
API to generate WGSL struct types at run-time. Given a list of attributes, it returns a virtual shader module with a real symbol table. This is materialized into shader code on demand, and can be exploded into individual accessor functions as well.
Hence data sources in Use.GPU can now have a format of T
or array<T>
with a WGSL shader module as the type parameter. I already had most of the pieces in place for this, but hadn\'t quite put it all together everywhere.
Using shader modules as the representation of types is very natural, as they carry all the WGSL attributes and GPU-only concepts. It goes far beyond what I had initially scoped for the linker, as it\'s all source-code-level, but it was worth it. The main limitation is that type inference only happens at link time, as binding shader modules together has to remain a fast and lazy op.
\\n\\nNative WGSL types are somewhat poorly aligned with the WebGPU API on the CPU side. A good chunk of @use-gpu/core
is lookup tables with info about formats and types, as well as alignment and size, so it can all be resolved at run-time. There\'s something similar for bind group creation, where it has to translate between a few different ways of saying the same thing.
The types I expose instead are simple: TextureSource
, StorageSource
and LambdaSource
. Everything you bind to a shader is either one of these, or a constant (by reference). They carry all the necessary metadata to derive a suitable binding and accessor.
That said, I cannot shield you from the limitations underneath. Texture formats can e.g. be renderable or not, filterable or not, writeable or not, and the specific mechanisms available to you vary. If this involves native depth buffers, you may need to use a full-screen render pass to copy data, instead of just calling copyTextureToTexture
. I run into this too, and can only provide a few more convenience hooks.
I did come up with a neat way to genericize these copy shaders, using the existing WGSL type inference I had, souped up a bit. This uses simple selector functions to serve the role of reassembling types. It\'s finally given me a concrete way to make \'root shaders\' (i.e. the entry points) generic enough to support all use. I may end up using something similar to handle the ordinary vertex and fragment entry points, which still have to be provided in various permutations.
\\n\\n* * *
\\n\\nPhew. Use.GPU is always a lot to go over. But its à la carte nature remains and that\'s great.
\\n\\nFor in-house use it\'s already useful, especially if you need a decent GPU on a desktop anyway. I have been using it for some client work, and it seems to be making people happy. If you want to go off-road from there, you can.
\\n\\nIt delivers on combining low-level shader code with its own stock components, without making you reinvent a lot of the wheels.
\\n\\nVisit usegpu.live for more and to view demos in a WebGPU capable browser.
\\n\\nPS: I upgraded the aging build of Jekyll that was driving this blog, so if you see anything out of the ordinary, please let me know.
\\n\\n","description":"Modern SSAO in a modern run-time Use.GPU 0.14 is out, so here\'s an update on my declarative/reactive rendering efforts.\\n\\nThe highlights in this release are:\\n\\ndramatic inspector viewing upgrades\\na modern ambient-occlusion (SSAO/GTAO) implementation\\nnewly revised render…","guid":"https://acko.net/blog/occlusion-with-bells-on","author":null,"authorUrl":null,"authorAvatar":null,"publishedAt":"2025-03-23T23:00:00.547Z","media":[{"url":"https://acko.net/files/use-gpu-14/cover.jpg","type":"photo","width":880,"height":496,"blurhash":"LDBCJc}rEj9v#?RRkUous9xZNaNc"},{"url":"https://acko.net/files/use-gpu-14/ssao-resolved.jpg","type":"photo","width":1922,"height":1152,"blurhash":"LJKUK7RPxtxu~qMxRPofI9xaIAM|"},{"url":"https://acko.net/files/use-gpu-14/mosaic.jpg","type":"photo","width":1165,"height":1005,"blurhash":"LQBz5~kRIT-=nzxvt8M_4msAxuIo"},{"url":"https://acko.net/files/use-gpu-14/inspect-1.png","type":"photo","width":564,"height":876,"blurhash":"L01fJ8_3InDiRjxb-;xurxi~Rjbs"},{"url":"https://acko.net/files/use-gpu-14/inspect-2.png","type":"photo","width":564,"height":876,"blurhash":"L238;n_4a%ITWBt7xut7IUM{ofxu"},{"url":"https://acko.net/files/use-gpu-14/inspect-filter.png","type":"photo","width":731,"height":57,"blurhash":"LiBXjOxut6M}ogj[j[ay9DM|R*xt"},{"url":"https://acko.net/files/use-gpu-14/inspect-3.png","type":"photo","width":564,"height":1146,"blurhash":"L93b]^o$MabBo[bFakoib4bJatab"},{"url":"https://acko.net/files/use-gpu-14/ssao-hemi.mov","type":"video","width":800,"height":540},{"url":"https://acko.net/files/use-gpu-14/ssao-sample.jpg","type":"photo","width":1537,"height":681,"blurhash":"LYP75@oyNGtQ_3kBofbG00j[s;ju"},{"url":"https://acko.net/files/use-gpu-14/ssao-motion.jpg","type":"photo","width":1028,"height":467,"blurhash":"LaLp8z5EJS?FPoF]OCsC1Yxqa#NH"},{"url":"https://acko.net/files/use-gpu-14/ssao-accum.jpg","type":"photo","width":1538,"height":685,"blurhash":"LjIQC|1V-qt8.8RlrxoeH@w~Rjk8"},{"url":"https://acko.net/files/use-gpu-14/ssao-depth.jpg","type":"photo","width":1540,"height":687,"blurhash":"LWJ7UrogSLxH}]Sea#oL5PS1bHWV"},{"url":"https://acko.net/files/use-gpu-14/ign.jpg","type":"photo","width":165,"height":165,"blurhash":"L3GbxH?b?b?bxut7Rjxu~qM{%M9F"},{"url":"https://acko.net/files/use-gpu-14/ssao-aliased.jpg","type":"photo","width":1114,"height":721,"blurhash":"LXIri=9W#t-Cspi{oekUTVXPa6WF"},{"url":"https://acko.net/files/use-gpu-14/ssao-resolve.jpg","type":"photo","width":1537,"height":683,"blurhash":"LZN^@-BMA9TD_Mo|kVkC00xHs;oL"},{"url":"https://acko.net/files/use-gpu-14/passes.jpg","type":"photo","width":321,"height":460,"blurhash":"L45hV@WC00ofxbxuoeWBait7ofay"},{"url":"https://acko.net/files/use-gpu-14/ssao-voxel.jpg","type":"photo","width":1026,"height":521,"blurhash":"LiQJfmt7j[t7~qWBWBj[9FWBRjWB"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"The Bouquet Residence","url":"https://acko.net/blog/the-bouquet-residence/","content":"\\n \\n The word \\"rant\\" is used far too often, and in various ways.\\n\\n
It\'s meant to imply aimless, angry venting.
\\n But often it means:
\\n Naming problems without proposing solutions,
this makes me feel confused.
\\n Naming problems and assigning blame,
this makes me feel bad.\\n \\n
I saw a remarkable pair of tweets the other day.
\\n\\nIn the wake of the outage, the CEO of CrowdStrike sent out a public announcement. It\'s purely factual. The scope of the problem is identified, the known facts are stated, and the logistics of disaster relief are set in motion.
\\n\\nMillions of computers were affected. This is the equivalent of a frazzled official giving a brief statement in the aftermath of an earthquake, directing people to the Red Cross.
\\n\\nEverything is basically on fire for everyone involved. Systems are failing everywhere, some critical, and quite likely people are panicking. The important thing is to give the technicians the information and tools to fix it, and for everyone else to do what they can, and stay out of the way.
\\n\\nIn response, a communication professional posted an \'improved\' version:
\\n\\nCredit where credit is due, she nailed the style. 10/10. It seems unobjectionable, at first. Let\'s go through, shall we?
\\n\\nFirst is that the CEO is \\"devastated.\\" A feeling. And they are personally going to ensure it\'s fixed for every single user.
\\n\\nThis focuses on the individual who is inconvenienced. Not the disaster. They take a moment out of their time to say they are so, so sorry a mistake was made. They have let you and everyone else down, and that shouldn\'t happen. That\'s their responsibility.
\\n\\nBy this point, the original statement had already told everyone the relevant facts. Here the technical details are left to the imagination. The writer\'s self-assigned job is to wrap the message in a more palatable envelope.
\\n\\nEveryone will be working \\"all day, all night, all weekends,\\" indeed, \\"however long it takes,\\" to avoid it happening again.
\\n\\nI imagine this is meant to be inspiring and reassuring. But if I was a CrowdStrike technician or engineer, I would find it demoralizing: the boss, who will actually be personally fixing diddly-squat, is saying that the long hours of others are a sacrifice they\'re willing to make.
\\n\\nPlus, CrowdStrike\'s customers are in the same boat: their technicians get volunteered too. They can\'t magically unbrick PCs from a distance, so \\"until it\'s fully fixed for every single user\\" would be a promise outsiders will have to keep. Lovely.
\\n\\nThere\'s even a punch line: an invitation to go contact them, the quickest way linked directly. It thanks people for reaching out.
\\n\\nIf everything is on fire, that includes the phone lines, the inboxes, and so on. The most stupid thing you could do in such a situation is to tell more people to contact you, right away. Don\'t encourage it! That\'s why the original statement refers to pre-existing lines of communication, internal representatives, and so on. The Support department would hate the CEO too.
\\n\\nIf you\'re wondering about the pictures, it\'s Hyacinth Bucket, from 90s UK sitcom Keeping Up Appearances, who would always insist \\"it\'s pronounced Bouquet.\\"
\\n\\nHyacinth\'s ambitions always landed her out of her depth, surrounded by upper-class people she\'s trying to impress, in the midst of an embarrassing disaster. Her increasingly desperate attempts to save face, which invariably made things worse, are the main source of comedy.
\\n\\nTry reading that second statement in her voice.
\\n\\n\\n\\n\\nI’m devastated to see the scale of today’s outage and will be personally working on it together with our team until it’s fully fixed for every single user.
\\n \\nBut I wanted to take a moment to come here and tell you that I am sorry. People around the world rely on us, and incidents like this can’t happen. This came from an error that ultimately is my responsibility.
\\n
I can hear it perfectly, telegraphing Britishness to restore dignity for all. If she were in tech she would give that statement.
\\n\\nIt\'s about reputation management first, projecting the image of competence and accountability. But she\'s giving the speech in front of a burning building, not realizing the entire exercise is futile. Worse, she thinks she\'s nailing it.
\\n\\nIf CrowdStrike had sent this out, some would\'ve applauded and called it an admirable example of wise and empathetic communication. Real leadership qualities.
\\n\\nBut it\'s the exact opposite. It focuses on the wrong things, it alienates the staff, and it definitely amplifies the chaos. It\'s Monty Python-esque.
\\n\\nApologizing is pointless here, the damage is already done. What matters is how severe it is and whether it could\'ve been avoided. This requires a detailed root-cause analysis and remedy. Otherwise you only have their word. Why would that re-assure you?
\\n\\nThe original restated the company\'s mission: security and stability. Those are the stakes to regain a modicum of confidence.
\\n\\nYou may think that I\'m reading too much into this. But I know the exact vibe on an engineering floor when the shit hits the fan. I also know how executives and staff without that experience end up missing the point entirely. I once worked for a Hyacinth Bucket. It\'s not an anecdote, it\'s allegory.
\\n\\nThey simply don\'t get the engineering mindset, and confuse authority with ownership. They step on everyone\'s toes without realizing, because they\'re constantly wearing clown shoes. Nobody tells them.
\\n\\nThe change in style between #1 and #2 is really a microcosm of the conflict that has been broiling in tech for ~15 years now. I don\'t mean the politics, but the shifting of norms, of language and behavior.
\\n\\nIt\'s framed as a matter of interpersonal style, which needs to be welcoming and inclusive. In practice this means they assert or demand that style #2 be the norm, even when #1 is advisable or required.
\\n\\nFactuality is seen as deficient, improper and primitive. It\'s a form of doublethink: everyone\'s preference is equally valid, except yours, specifically.
\\n\\nBut the difference is not a preference. It\'s about what actually works and what doesn\'t. Style #1 is aimed at the people who have to fix it. Style #2 is aimed at the people who can\'t do anything until it\'s fixed. Who should they be reaching out to?
\\n\\nIn #2, communication becomes an end in itself, not a means of conveying information. It\'s about being seen saying the words, not living them. Poking at the statement makes it fall apart.
\\n\\nWhen this becomes the norm in a technical field, it has deep consequences:
\\n\\nInevitably, quiet competence is replaced with gaudy chaos. Everyone says they\'re sorry and responsible, but nobody actually is. Nobody wants to resign either. Sound familiar?
\\n\\nThe elephant in the room is that #1 is very masculine, while #2 is more feminine. When you hear \\"women are more empathetic communicators\\", this is what it means. They tend to focus on the individual and their relation to them, not the team as a whole and its mission.
\\n\\nComplaints that tech is too \\"male dominated\\" and \\"notoriously hostile to women\\" are often just this. Tech was always full of types who won\'t preface their proposals and criticisms with fluff, and instead lean into autism. When you\'re used to being pandered to, neutrality feels like vulgarity.
\\n\\nThe notable exceptions are rare and usually have an exasperating lead up. Tech is actually one of the most accepting and egalitarian fields around. The maintainers do a mostly thankless job.
\\n\\n\\"Oh so you\'re saying there\'s no misogyny in tech?\\" No I\'m just saying misogyny doesn\'t mean \\"something 1 woman hates\\".
\\n\\nThe tone is really a distraction. If someone drops an analysis, saying shit or get off the pot, even very kindly and patiently, some will still run away screaming. Like an octopus spraying ink, they\'ll deploy a nasty form of #2 as a distraction. That\'s the real issue.
\\n\\nMany techies, in their naiveté, believed the cultural reformers when they showed up to gentrify them. They obediently branded heretics like James Damore, and burned witches like Richard Stallman. Thanks to racism, words like \'master\' and \'slave\' are now off-limits as technical terms. Ironic, because millions of computers just crashed because they worked exactly like that.
\\n\\nThe cope is to pretend that nothing has truly changed yet, and more reform is needed. In fact, everything has already changed. Tech forums used to be crucibles for distilling insight, but now they are guarded jealously by people more likely to flag and ban than strongly disagree.
\\n\\nI once got flagged on HN because I pointed out Twitter\'s mass lay-offs were a response to overhiring, and that people were rooting for the site to fail after Musk bought it. It suggested what we all know now: that the company would not implode after trimming the dead weight, and that they\'d never forgive him for it.
\\n\\nDiversity is now associated with incompetence, because incompetent people have spent over a decade reaching for it as an excuse. In their attempts to fight stereotypes, they ensured the stereotypes came true.
\\n\\nThe outcry tends to be: \\"We do all the same things you do, but still we get treated differently!\\" But they start from the conclusion and work their way backwards. This is what the rewritten statement does: it tries to fix the relationship before fixing the problem.
\\n\\nThe average woman and man actually do things very differently in the first place. Individual men and women choose. And others respond accordingly. The people who build and maintain the world\'s infrastructure prefer the masculine style for a reason: it keeps civilization running, and helps restore it when it breaks. A disaster announcement does not need to be relatable, it needs to be effective.
\\n\\nFurthermore, if the job of shoveling shit falls on you, no amount of flattery or oversight will make that more pleasant. It really won\'t. Such commentary is purely for the benefit of the ones watching and trying to look busy. It makes it worse, stop pretending otherwise.
\\n\\nThere\'s little loyalty in tech companies nowadays, and it\'s no surprise. Project and product managers are acting more like demanding clients to their own team, than leaders. \\"As a user, I want...\\" Yes, but what are you going to do about it? Do you even know where to start?
\\n\\nWhat\'s perceived as a lack of sensitivity is actually the presence of sensibility. It\'s what connects the words to the reality on the ground. It does not need to be improved or corrected, it just needs to be respected. And yes it\'s a matter of gender, because bashing men and masculine norms has become a jolly recreational sport in the overculture. Mature women know it.
\\n\\nIt seems impossible to admit. The entire edifice of gender equality depends on there not being a single thing men are actually better at, even just on average. Where men and women\'s instincts differ, women must be right.
\\n\\nIt\'s childish, and not harmless either. It dares you to call it out, so they can then play the wounded victim, and paint you as the unreasonable asshole who is mean. This is supposed to invalidate the argument.
\\n\\n* * *
\\n\\nThis post is of course a giant cannon pointing in the opposite direction, sitting on top of a wall. Its message will likely fly over the reformers\' heads.
\\n\\nIf they read it at all, they\'ll selectively quote or paraphrase, call me a tech-bro, and spool off some sentences they overheard, like an LLM. It\'s why they adore AI, and want it to be exactly as sycophantic as them. They don\'t care that it makes stuff up wholesale, because it makes them look and feel competent. It will never tell them to just fuck off already.
\\n\\nThink less about what is said, more about what is being done. Otherwise the next CrowdStrike will probably be worse.
\\n\\n\\n\\n\\n\\n\\n \\n The word \\"rant\\" is used far too often, and in various ways.\\n\\n
It\'s meant to imply aimless, angry venting.
\\n But often it means:
\\n Naming problems without proposing solutions,
this makes me feel confused.
\\n Naming problems and assigning blame,
this makes me feel bad.\\n \\n
I saw a remarkable pair of tweets the other day.
\\n\\nIn the wake of the outage, the CEO of CrowdStrike sent out a public announcement. It\'s purely factual. The scope of the problem is identified, the known facts are stated, and the logistics of disaster relief are set in motion.
\\n\\nMillions of computers were affected. This is the equivalent of a frazzled official giving a brief statement in the aftermath of an earthquake, directing people to the Red Cross.
\\n\\nEverything is basically on fire for everyone involved. Systems are failing everywhere, some critical, and quite likely people are panicking. The important thing is to give the technicians the information and tools to fix it, and for everyone else to do what they can, and stay out of the way.
\\n\\nIn response, a communication professional posted an \'improved\' version:
\\n\\nCredit where credit is due, she nailed the style. 10/10. It seems unobjectionable, at first. Let\'s go through, shall we?
\\n\\nFirst is that the CEO is \\"devastated.\\" A feeling. And they are personally going to ensure it\'s fixed for every single user.
\\n\\nThis focuses on the individual who is inconvenienced. Not the disaster. They take a moment out of their time to say they are so, so sorry a mistake was made. They have let you and everyone else down, and that shouldn\'t happen. That\'s their responsibility.
\\n\\nBy this point, the original statement had already told everyone the relevant facts. Here the technical details are left to the imagination. The writer\'s self-assigned job is to wrap the message in a more palatable envelope.
\\n\\nEveryone will be working \\"all day, all night, all weekends,\\" indeed, \\"however long it takes,\\" to avoid it happening again.
\\n\\nI imagine this is meant to be inspiring and reassuring. But if I was a CrowdStrike technician or engineer, I would find it demoralizing: the boss, who will actually be personally fixing diddly-squat, is saying that the long hours of others are a sacrifice they\'re willing to make.
\\n\\nPlus, CrowdStrike\'s customers are in the same boat: their technicians get volunteered too. They can\'t magically unbrick PCs from a distance, so \\"until it\'s fully fixed for every single user\\" would be a promise outsiders will have to keep. Lovely.
\\n\\nThere\'s even a punch line: an invitation to go contact them, the quickest way linked directly. It thanks people for reaching out.
\\n\\nIf everything is on fire, that includes the phone lines, the inboxes, and so on. The most stupid thing you could do in such a situation is to tell more people to contact you, right away. Don\'t encourage it! That\'s why the original statement refers to pre-existing lines of communication, internal representatives, and so on. The Support department would hate the CEO too.
\\n\\nIf you\'re wondering about the pictures, it\'s Hyacinth Bucket, from 90s UK sitcom Keeping Up Appearances, who would always insist \\"it\'s pronounced Bouquet.\\"
\\n\\nHyacinth\'s ambitions always landed her out of her depth, surrounded by upper-class people she\'s trying to impress, in the midst of an embarrassing disaster. Her increasingly desperate attempts to save face, which invariably made things worse, are the main source of comedy.
\\n\\nTry reading that second statement in her voice.
\\n\\n\\n\\n\\nI’m devastated to see the scale of today’s outage and will be personally working on it together with our team until it’s fully fixed for every single user.
\\n \\nBut I wanted to take a moment to come here and tell you that I am sorry. People around the world rely on us, and incidents like this can’t happen. This came from an error that ultimately is my responsibility.
\\n
I can hear it perfectly, telegraphing Britishness to restore dignity for all. If she were in tech she would give that statement.
\\n\\nIt\'s about reputation management first, projecting the image of competence and accountability. But she\'s giving the speech in front of a burning building, not realizing the entire exercise is futile. Worse, she thinks she\'s nailing it.
\\n\\nIf CrowdStrike had sent this out, some would\'ve applauded and called it an admirable example of wise and empathetic communication. Real leadership qualities.
\\n\\nBut it\'s the exact opposite. It focuses on the wrong things, it alienates the staff, and it definitely amplifies the chaos. It\'s Monty Python-esque.
\\n\\nApologizing is pointless here, the damage is already done. What matters is how severe it is and whether it could\'ve been avoided. This requires a detailed root-cause analysis and remedy. Otherwise you only have their word. Why would that re-assure you?
\\n\\nThe original restated the company\'s mission: security and stability. Those are the stakes to regain a modicum of confidence.
\\n\\nYou may think that I\'m reading too much into this. But I know the exact vibe on an engineering floor when the shit hits the fan. I also know how executives and staff without that experience end up missing the point entirely. I once worked for a Hyacinth Bucket. It\'s not an anecdote, it\'s allegory.
\\n\\nThey simply don\'t get the engineering mindset, and confuse authority with ownership. They step on everyone\'s toes without realizing, because they\'re constantly wearing clown shoes. Nobody tells them.
\\n\\nThe change in style between #1 and #2 is really a microcosm of the conflict that has been broiling in tech for ~15 years now. I don\'t mean the politics, but the shifting of norms, of language and behavior.
\\n\\nIt\'s framed as a matter of interpersonal style, which needs to be welcoming and inclusive. In practice this means they assert or demand that style #2 be the norm, even when #1 is advisable or required.
\\n\\nFactuality is seen as deficient, improper and primitive. It\'s a form of doublethink: everyone\'s preference is equally valid, except yours, specifically.
\\n\\nBut the difference is not a preference. It\'s about what actually works and what doesn\'t. Style #1 is aimed at the people who have to fix it. Style #2 is aimed at the people who can\'t do anything until it\'s fixed. Who should they be reaching out to?
\\n\\nIn #2, communication becomes an end in itself, not a means of conveying information. It\'s about being seen saying the words, not living them. Poking at the statement makes it fall apart.
\\n\\nWhen this becomes the norm in a technical field, it has deep consequences:
\\n\\nInevitably, quiet competence is replaced with gaudy chaos. Everyone says they\'re sorry and responsible, but nobody actually is. Nobody wants to resign either. Sound familiar?
\\n\\nThe elephant in the room is that #1 is very masculine, while #2 is more feminine. When you hear \\"women are more empathetic communicators\\", this is what it means. They tend to focus on the individual and their relation to them, not the team as a whole and its mission.
\\n\\nComplaints that tech is too \\"male dominated\\" and \\"notoriously hostile to women\\" are often just this. Tech was always full of types who won\'t preface their proposals and criticisms with fluff, and instead lean into autism. When you\'re used to being pandered to, neutrality feels like vulgarity.
\\n\\nThe notable exceptions are rare and usually have an exasperating lead up. Tech is actually one of the most accepting and egalitarian fields around. The maintainers do a mostly thankless job.
\\n\\n\\"Oh so you\'re saying there\'s no misogyny in tech?\\" No I\'m just saying misogyny doesn\'t mean \\"something 1 woman hates\\".
\\n\\nThe tone is really a distraction. If someone drops an analysis, saying shit or get off the pot, even very kindly and patiently, some will still run away screaming. Like an octopus spraying ink, they\'ll deploy a nasty form of #2 as a distraction. That\'s the real issue.
\\n\\nMany techies, in their naiveté, believed the cultural reformers when they showed up to gentrify them. They obediently branded heretics like James Damore, and burned witches like Richard Stallman. Thanks to racism, words like \'master\' and \'slave\' are now off-limits as technical terms. Ironic, because millions of computers just crashed because they worked exactly like that.
\\n\\nThe cope is to pretend that nothing has truly changed yet, and more reform is needed. In fact, everything has already changed. Tech forums used to be crucibles for distilling insight, but now they are guarded jealously by people more likely to flag and ban than strongly disagree.
\\n\\nI once got flagged on HN because I pointed out Twitter\'s mass lay-offs were a response to overhiring, and that people were rooting for the site to fail after Musk bought it. It suggested what we all know now: that the company would not implode after trimming the dead weight, and that they\'d never forgive him for it.
\\n\\nDiversity is now associated with incompetence, because incompetent people have spent over a decade reaching for it as an excuse. In their attempts to fight stereotypes, they ensured the stereotypes came true.
\\n\\nThe outcry tends to be: \\"We do all the same things you do, but still we get treated differently!\\" But they start from the conclusion and work their way backwards. This is what the rewritten statement does: it tries to fix the relationship before fixing the problem.
\\n\\nThe average woman and man actually do things very differently in the first place. Individual men and women choose. And others respond accordingly. The people who build and maintain the world\'s infrastructure prefer the masculine style for a reason: it keeps civilization running, and helps restore it when it breaks. A disaster announcement does not need to be relatable, it needs to be effective.
\\n\\nFurthermore, if the job of shoveling shit falls on you, no amount of flattery or oversight will make that more pleasant. It really won\'t. Such commentary is purely for the benefit of the ones watching and trying to look busy. It makes it worse, stop pretending otherwise.
\\n\\nThere\'s little loyalty in tech companies nowadays, and it\'s no surprise. Project and product managers are acting more like demanding clients to their own team, than leaders. \\"As a user, I want...\\" Yes, but what are you going to do about it? Do you even know where to start?
\\n\\nWhat\'s perceived as a lack of sensitivity is actually the presence of sensibility. It\'s what connects the words to the reality on the ground. It does not need to be improved or corrected, it just needs to be respected. And yes it\'s a matter of gender, because bashing men and masculine norms has become a jolly recreational sport in the overculture. Mature women know it.
\\n\\nIt seems impossible to admit. The entire edifice of gender equality depends on there not being a single thing men are actually better at, even just on average. Where men and women\'s instincts differ, women must be right.
\\n\\nIt\'s childish, and not harmless either. It dares you to call it out, so they can then play the wounded victim, and paint you as the unreasonable asshole who is mean. This is supposed to invalidate the argument.
\\n\\n* * *
\\n\\nThis post is of course a giant cannon pointing in the opposite direction, sitting on top of a wall. Its message will likely fly over the reformers\' heads.
\\n\\nIf they read it at all, they\'ll selectively quote or paraphrase, call me a tech-bro, and spool off some sentences they overheard, like an LLM. It\'s why they adore AI, and want it to be exactly as sycophantic as them. They don\'t care that it makes stuff up wholesale, because it makes them look and feel competent. It will never tell them to just fuck off already.
\\n\\nThink less about what is said, more about what is being done. Otherwise the next CrowdStrike will probably be worse.
\\n\\n\\n\\n\\n\\n\\n \\n\\n\\n\\n \\"I do not like your software sir,
\\n\\n
\\n your architecture\'s poor.\\n\\n Your users can\'t do anything,
\\n\\n
\\n unless you code some more.\\n\\n This isn\'t how it used to be,
\\n\\n
\\n we had this figured out.\\n\\n But then you had to mess it up
\\n \\n
\\n by moving into clouds.\\"\\n
There\'s a certain kind of programmer. Let\'s call him Stanley.
\\n\\nStanley has been around for a while, and has his fair share of war stories. The common thread is that poorly conceived and specced solutions lead to disaster, or at least, ongoing misery. As a result, he has adopted a firm belief: it should be impossible for his program to reach an invalid state.
\\n\\nStanley loves strong and static typing. He\'s a big fan of pattern matching, and enums, and discriminated unions, which allow correctness to be verified at compile time. He also has strong opinions on errors, which must be caught, logged and prevented. He uses only ACID-compliant databases, wants foreign keys and triggers to be enforced, and wraps everything in atomic transactions.
\\n\\nHe hates any source of uncertainty or ambiguity, like untyped JSON or plain-text markup. His APIs will accept data only in normalized and validated form. When you use a Stanley lib, and it doesn\'t work, the answer will probably be: \\"you\'re using it wrong.\\"
\\n\\nStanley is most likely a back-end or systems-level developer. Because nirvana in front-end development is reached when you understand that this view of software is not just wrong, but fundamentally incompatible with the real world.
\\n\\nI will prove it.
\\n\\nTake a text editor. What happens if you press the up and down arrows?
\\n\\nThe keyboard cursor (aka caret) moves up and down. Duh. Except it also moves left and right.
\\n\\nThe editor state at the start has the caret on line 1 column 6. Pressing down will move it to line 2 column 6. But line 2 is too short, so the caret is forcibly moved left to column 1. Then, pressing down again will move it back to column 6.
\\n\\nIt should be obvious that any editor that didn\'t remember which column you were actually on would be a nightmare to use. You know it in your bones. Yet this only works because the editor allows the caret to be placed on a position that \\"does not exist.\\" What is the caret state in the middle? It is both column 1 and column 6.
\\n\\nTo accommodate this, you need more than just a View
that is a pure function of a State
, as is now commonly taught. Rather, you need an Intent
, which is the source of truth that you mutate... and which is then parsed and validated into a State
. Only then can it be used by the View
to render the caret in the right place.
To edit the intent, aka what a classic Controller
does, is a bit tricky. When you press left/right, it should determine the new Intent.column
based on the validated State.column +/- 1
. But when you press up/down, it should keep the Intent.column
you had before and instead change only Intent.line
. New intent is a mixed function of both previous intent and previous state.
The general pattern is that you reuse Intent
if it doesn\'t change, but that new computed Intent
should be derived from State
. Note that you should still enforce normal validation of Intent.column
when editing too: you don\'t allow a user to go past the end of a line. Any new intent should be as valid as possible, but old intent should be preserved as is, even if non-sensical or inapplicable.
Functionally, for most of the code, it really does look and feel as if the state is just State
, which is valid. It\'s just that when you make 1 state change, the app may decide to jump into a different State
than one would think. When this happens, it means some old intent first became invalid, but then became valid again due to a subsequent intent/state change.
This is how applications actually work IRL. FYI.
\\n\\nI chose a text editor as an example because Stanley can\'t dismiss this as just frivolous UI polish for limp wristed homosexuals. It\'s essential that editors work like this.
\\n\\nThe pattern is far more common than most devs realize:
\\n\\nAll of these involve storing and preserving something unknown, invalid or unused, and bringing it back into play later.
\\n\\nMore so, if software matches your expected intent, it\'s a complete non-event. What looks like a \\"surprise hidden state transition\\" to a programmer is actually the exact opposite. It would be an unpleasant surprise if that extra state transition didn\'t occur. It would only annoy users: they already told the software what they wanted, but it keeps forgetting.
\\n\\nThe ur-example is how nested popup menus should work: good implementations track the motion of the cursor so you can move diagonally from parent to child, without falsely losing focus:
\\n\\nThis is an instance of the goalkeeper\'s curse: people rarely compliment or notice the goalkeeper if they do their job, only if they mess up. Successful applications of this principle are doomed to remain unnoticed and unstudied.
\\n\\nValidation is not something you do once, discarding the messy input and only preserving the normalized output. It\'s something you do continuously and non-destructively, preserving the mess as much as possible. It\'s UI etiquette: the unspoken rules that everyone expects but which are mostly undocumented folklore.
\\n\\nThis poses a problem for most SaaS in the wild, both architectural and existential. Most APIs will only accept mutations that are valid. The goal is for the database to be a sequence of fully valid states:
\\n\\nThe smallest possible operation in the system is a fully consistent transaction. This flattens any prior intent.
\\n\\nIn practice, many software deviates from this ad-hoc. For example, spreadsheets let you create cyclic references, which is by definition invalid. The reason it must let you do this is because fixing one side of a cyclic reference also fixes the other side. A user wants and needs to do these operations in any order. So you must allow a state transition through an invalid state:
\\n\\nThis requires an effective Intent/State split, whether formal or informal.
\\n\\nBecause cyclic references can go several levels deep, identifying one cyclic reference may require you to spider out the entire dependency graph. This is functionally equivalent to identifying all cyclic references—dixit Dijkstra. Plus, you need to produce sensible, specific error messages. Many \\"clever\\" algorithmic tricks fail this test.
\\n\\nNow imagine a spreadsheet API that doesn\'t allow for any cyclic references ever. This still requires you to validate the entire resulting model, just to determine if 1 change is allowed. It still requires a general validate(Intent)
. In short, it means your POST and PUT request handlers need to potentially call all your business logic.
That seems overkill, so the usual solution is bespoke validators for every single op. If the business logic changes, there is a risk your API will now accept invalid intent. And the app was not built for that.
\\n\\nIf you flip it around and assume intent will go out-of-bounds as a normal matter, then you never have this risk. You can write the validation in one place, and you reuse it for every change as a normal matter of data flow.
\\n\\nNote that this is not cowboy coding. Records and state should not get irreversibly corrupted, because you only ever use valid inputs in computations. If the system is multiplayer, distributed changes should still be well-ordered and/or convergent. But the data structures you\'re encoding should be, essentially, entirely liberal to your user\'s needs.
\\n\\nConsider git. Here, a \\"unit of intent\\" is just a diff applied to a known revision ID. When something\'s wrong with a merge, it doesn\'t crash, or panic, or refuse to work. It just enters a conflict state. This state is computed by merging two incompatible intents.
\\n\\nIt\'s a dirty state that can\'t be turned into a clean commit without human intervention. This means git must continue to work, because you need to use git to clean it up. So git is fully aware when a conflict is being resolved.
\\n\\nAs a general rule, the cases where you actually need to forbid a mutation which satisfies all the type and access constraints are small. A good example is trying to move a folder inside itself: the file system has to remain a sensibly connected tree. Enforcing the uniqueness of names is similar, but also comes with a caution: falsehoods programmers believe about names. Adding (Copy)
to a duplicate name is usually better than refusing to accept it, and most names in real life aren\'t unique at all. Having user-facing names actually requires creating tools and affordances for search, renaming references, resolving duplicates, and so on.
Even among front-end developers, few people actually grok this mental model of a user. It\'s why most React(-like) apps in the wild are spaghetti, and why most blog posts about React gripes continue to miss the bigger picture. Doing React (and UI) well requires you to unlearn old habits and actually design your types and data flow so it uses potentially invalid input as its single source of truth. That way, a one-way data flow can enforce the necessary constraints on the fly.
\\n\\nThe way Stanley likes to encode and mutate his data is how programmers think about their own program: it should be bug-free and not crash. The mistake is to think that this should also apply to any sort of creative process that program is meant to enable. It would be like making an IDE that only allows you to save a file if the code compiles and passes all the tests.
\\n\\nCoding around intent is a very hard thing to teach, because it can seem overwhelming. But what\'s overwhelming is not doing this. It leads to codebases where every new feature makes ongoing development harder, because no part of the codebase is ever finished. You will sprinkle copies of your business logic all over the place, in the form of request validation, optimistic local updaters, and guess-based cache invalidation.
\\n\\nIf this is your baseline experience, your estimate of what is needed to pull this off will simply be wrong.
\\n\\nIn the traditional MVC model, intent is only handled at the individual input widget or form level. e.g. While typing a number, the intermediate representation is a string. This may be empty, incomplete or not a number, but you temporarily allow that.
\\n\\nI\'ve never seen people formally separate Intent
from State
in an entire front-end. Often their state is just an adhoc mix of both, where validation constraints are relaxed in the places where it was most needed. They might just duplicate certain fields to keep a validated
and unvalidated
variant side by side.
There is one common exception. In a React-like, when you do a useMemo
with a derived computation of some state, this is actually a perfect fit. The eponymous useState
actually describes Intent
, not State
, because the derived state is ephemeral. This is why so many devs get lost here.
const state = useMemo(\\n () => validate(intent),\\n [intent]\\n);
\\n\\nTheir usual instinct is that every action that has knock-on effects should be immediately and fully realized, as part of one transaction. Only, they discover some of those knock-on effects need to be re-evaluated if certain circumstances change. Often to do so, they need to undo and remember what it was before. This is then triggered anew via a bespoke effect, which requires a custom trigger and mutation. If they\'d instead deferred the computation, it could have auto-updated itself, and they would\'ve still had the original data to work with.
\\n\\ne.g. In a WYSIWYG scenario, you often want to preview an operation as part of mouse hovering or dragging. It should look like the final result. You don\'t need to implement custom previewing and rewinding code for this. You just need the ability to layer on some additional ephemeral intent on top of the intent that is currently committed. Rewinding just means resetting that extra intent back to empty.
\\n\\nYou can make this easy to use by treating previews as a special kind of transaction: now you can make preview states with the same code you use to apply the final change. You can also auto-tag the created objects as being preview-only, which is very useful. That is: you can auto-translate editing intent into preview intent, by messing with the contents of a transaction. Sounds bad, is actually good.
\\n\\nThe same applies to any other temporary state, for example, highlighting of elements. Instead of manually changing colors, and creating/deleting labels to pull this off, derive the resolved style just-in-time. This is vastly simpler than doing it all on 1 classic retained model. There, you run the risk of highlights incorrectly becoming sticky, or shapes reverting to the wrong style when un-highlighted. You can architect it so this is simply impossible.
\\n\\nThe trigger vs memo problem also happens on the back-end, when you have derived collections. Each object of type A must have an associated type B, created on-demand for each A. What happens if you delete an A? Do you delete the B? Do you turn the B into a tombstone? What if the relationship is 1-to-N, do you need to garbage collect?
\\n\\nIf you create invisible objects behind the scenes as a user edits, and you never tell them, expect to see a giant mess as a result. It\'s crazy how often I\'ve heard engineers suggest a user should only be allowed to create something, but then never delete it, as a \\"solution\\" to this problem. Everyday undo/redo precludes it. Don\'t be ridiculous.
\\n\\nThe problem is having an additional layer of bookkeeping you didn\'t need. The source of truth was collection A, but you created a permanent derived collection B. If you instead make B ephemeral, derived via a stateless computation, then the problem goes away. You can still associate data with B records, but you don\'t treat B as the authoritative source for itself. This is basically what a WeakMap
is.
In database land this can be realized with a materialized view, which can be incremental and subscribed to. Taken to its extreme, this turns into event-based sourcing, which might seem like a panacea for this mindset. But in most cases, the latter is still a system by and for Stanley. The event-based nature of those systems exists to support housekeeping tasks like migration, backup and recovery. Users are not supposed to be aware that this is happening. They do not have any view into the event log, and cannot branch and merge it. The exceptions are extremely rare.
\\n\\nIt\'s not a system for working with user intent, only for flattening it, because it\'s append-only. It has a lot of the necessary basic building blocks, but substitutes programmer intent for user intent.
\\n\\nWhat\'s most nefarious is that the resulting tech stacks are often quite big and intricate, involving job queues, multi-layered caches, distribution networks, and more. It\'s a bunch of stuff that Stanley can take joy and pride in, far away from users, with \\"hard\\" engineering challenges. Unlike all this *ugh* JavaScript, which is always broken and unreliable and uninteresting.
\\n\\nExcept it\'s only needed because Stanley only solved half the problem, badly.
\\n\\nWhen factored in from the start, it\'s actually quite practical to split Intent
from State
, and it has lots of benefits. Especially if State
is just a more constrained version of the same data structures as Intent
. This doesn\'t need to be fully global either, but it needs to encompass a meaningful document or workspace to be useful.
It does create an additional problem: you now have two kinds of data in circulation. If reading or writing requires you to be aware of both Intent
and State
, you\'ve made your code more complicated and harder to reason about.
More so, making a new Intent
requires a copy of the old Intent
, which you mutate or clone. But you want to avoid passing Intent
around in general, because it\'s fishy data. It may have the right types, but the constraints and referential integrity aren\'t guaranteed. It\'s a magnet for the kind of bugs a type-checker won\'t catch.
I\'ve published my common solution before: turn changes into first-class values, and make a generic update of type Update<T>
be the basic unit of change. As a first approximation, consider a shallow merge {...value, ...update}
. This allows you to make an updateIntent(update)
function where update
only specifies the fields that are changing.
In other words, Update<Intent>
looks just like Update<State>
and can be derived 100% from State
, without Intent
. Only one place needs to have access to the old Intent
, all other code can just call that. You can make an app intent-aware without complicating all the code.
If your state is cleaved along orthogonal lines, then this is all you need. i.e. If column
and line
are two separate fields, then you can selectively change only one of them. If they are stored as an XY
tuple or vector, now you need to be able to describe a change that only affects either the X or Y component.
const value = {\\n hello: \'text\',\\n foo: { bar: 2, baz: 4 },\\n};\\n\\nconst update = {\\n hello: \'world\',\\n foo: { baz: 50 },\\n};\\n\\nexpect(\\n patch(value, update)\\n).toEqual({\\n hello: \'world\',\\n foo: { bar: 2, baz: 50 },\\n});
\\n\\nSo in practice I have a function patch(value, update)
which implements a comprehensive superset of a deep recursive merge, with full immutability. It doesn\'t try to do anything fancy with arrays or strings, they\'re just treated as atomic values. But it allows for precise overriding of merging behavior at every level, as well as custom lambda-based updates. You can patch tuples by index, but this is risky for dynamic lists. So instead you can express e.g. \\"append item to list\\" without the entire list, as a lambda.
I\'ve been using patch
for years now, and the uses are myriad. To overlay a set of overrides onto a base template, patch(base, overrides)
is all you need. It\'s the most effective way I know to erase a metric ton of {...splats}
and ?? defaultValues
and != null
from entire swathes of code. This is a real problem.
You could also view this as a \\"poor man\'s OT\\", with the main distinction being that a patch update
only describes the new state, not the old state. Such updates are not reversible on their own. But they are far simpler to make and apply.
It can still power a global undo/redo system, in combination with its complement diff(A, B)
: you can reverse an update by diffing in the opposite direction. This is an operation which is formalized and streamlined into revise(…)
, so that it retains the exact shape of the original update, and doesn\'t require B
at all. The structure of the update is sufficient information: it too encodes some intent behind the change.
With patch
you also have a natural way to work with changes and conflicts as values. The earlier WYSIWIG scenario is just patch(commited, ephemeral)
with bells on.
The net result is that mutating my intent or state is as easy as doing a {...value, ...update}
splat, but I\'m not falsely incentivized to flatten my data structures.
Instead it frees you up to think about what the most practical schema actually is from the data\'s point of view. This is driven by how the user wishes to edit it, because that\'s what you will connect it to. It makes you think about what a user\'s workspace actually is, and lets you align boundaries in UX and process with boundaries in data structure.
\\n\\nRemember: most classic \\"data structures\\" are not about the structure of data at all. They serve as acceleration tools to speed up specific operations you need on that data. Having the reads and writes drive the data design was always part of the job. What\'s weird is that people don\'t apply that idea end-to-end, from database to UI and back.
\\n\\nSQL tables are shaped the way they are because it enables complex filters and joins. However, I find this pretty counterproductive: it produces derived query results that are difficult to keep up to date on a client. They also don\'t look like any of the data structures I actually want to use in my code.
\\n\\n\\nThis points to a very under-appreciated problem: it is completely pointless to argue about schemas and data types without citing specific domain logic and code that will be used to produce, edit and consume it. Because that code determines which structures you are incentivized to use, and which structures will require bespoke extra work.
\\n\\nFrom afar, column
and line
are just XY coordinates. Just use a 2-vector. But once you factor in the domain logic and etiquette, you realize that the horizontal and vertical directions have vastly different rules applied to them, and splitting might be better. Which one do you pick?
This applies to all data. Whether you should put items in a List<T>
or a Map<K, V>
largely depends on whether the consuming code will loop over it, or need random access. If an API only provides one, consumers will just build the missing Map
or List
as a first step. This is O(n log n)
either way, because of sorting.
The method you use to read or write your data shouldn\'t limit use of everyday structure. Not unless you have a very good reason. But this is exactly what happens.
\\n\\nA lot of bad choices in data design come down to picking the \\"wrong\\" data type simply because the most appropriate one is inconvenient in some cases. This then leads to Conway\'s law, where one team picks the types that are most convenient only for them. The other teams are stuck with it, and end up writing bidirectional conversion code around their part, which will never be removed. The software will now always have this shape, reflecting which concerns were considered essential. What color are your types?
\\n\\n{\\n order: [4, 11, 9, 5, 15, 43],\\n values: {\\n 4: {...},\\n 5: {...},\\n 9: {...},\\n 11: {...},\\n 15: {...},\\n 43: {...},\\n },\\n);
\\n\\nFor List
vs Map
, you can have this particular cake and eat it too. Just provide a List<Id>
for the order
and a Map<Id, T>
for the values
. If you structure a list or tree this way, then you can do both iteration and ID-based traversal in the most natural and efficient way. Don\'t underestimate how convenient this can be.
This also has the benefit that \\"re-ordering items\\" and \\"editing items\\" are fully orthogonal operations. It decomposes the problem of \\"patching a list of objects\\" into \\"patching a list of IDs\\" and \\"patching N separate objects\\". It makes code for manipulating lists and trees universal. It lets you to decide on a case by case basis whether you need to garbage collect the map, or whether preserving unused records is actually desirable.
\\n\\nLimiting it to ordinary JSON or JS types, rather than going full-blown OT or CRDT, is a useful baseline. With sensible schema design, at ordinary editing rates, CRDTs are overkill compared to the ability to just replay edits, or notify conflicts. This only requires version numbers and retries.
\\n\\nUsers need those things anyway: just because a CRDT converges when two people edit, doesn\'t mean the result is what either person wants. The only case where OTs/CRDTs are absolutely necessary is rich-text editing, and you need bespoke UI solutions for that anyway. For simple text fields, last-write-wins is perfectly fine, and also far superior to what 99% of RESTy APIs do.
\\n\\nA CRDT is just a mechanism that translates partially ordered intents into a single state. Like, it\'s cool that you can make CRDT counters and CRDT lists and whatnot... but each CRDT implements only one particular resolution strategy. If it doesn\'t produce the desired result, you\'ve created invalid intent no user expected. With last-write-wins, you at least have something 1 user did intend. Whether this is actually destructive or corrective is mostly a matter of schema design and minimal surface area, not math.
\\n\\nThe main thing that OTs and CRDTs do well is resolve edits on ordered sequences, like strings. If two users are typing text in the same doc, edits higher-up will shift edits down below, which means the indices change when rebased. But if you are editing structured data, you can avoid referring to indices entirely, and just use IDs instead. This sidesteps the issue, like splitting order
from values
.
For the order
, there is a simple solution: a map with a fractional index, effectively a dead-simple list CRDT. It just comes with some overhead.
Using a CRDT for string editing might not even be enough. Consider Google Docs-style comments anchored to that text: their indices also need to shift on every edit. Now you need a bespoke domain-aware CRDT. Or you work around it by injecting magic markers into the text. Either way, it seems non-trivial to decouple a CRDT from the specific target domain of the data inside. The constraints get mixed in.
\\n\\nIf you ask me, this is why the field of real-time web apps is still in somewhat of a rut. It\'s mainly viewed as a high-end technical problem: how do we synchronize data structures over a P2P network without any data conflicts? What they should be asking is: what is the minimal amount of structure we need to reliably synchronize, so that users can have a shared workspace where intent is preserved, and conflicts are clearly signposted. And how should we design our schemas, so that our code can manipulate the data in a straightforward and reliable way? Fixing non-trivial user conflicts is simply not your job.
\\n\\nMost SaaS out there doesn\'t need any of this technical complexity. Consider that a good multiplayer app requires user presence and broadcast anyway. The simplest solution is just a persistent process on a single server coordinating this, one per live workspace. It\'s what most MMOs do. In fast-paced video games, this even involves lag compensation. Reliable ordering is not the big problem.
\\n\\nThe situations where this doesn\'t scale, or where you absolutely must be P2P, are a minority. If you run into them, you must be doing very well. The solution that I\'ve sketched out here is explicitly designed so it can comfortably be done by small teams, or even just 1 person.
\\n\\nThe (private) CAD app I showed glimpses of above is entirely built this way. It\'s patch all the way down and it\'s had undo/redo from day 1. It also has a developer mode where you can just edit the user-space part of the data model, and save/load it.
\\n\\nWhen the in-house designers come to me with new UX requests, they often ask: \\"Is it possible to do ____?\\" The answer is never a laborious sigh from a front-end dev with too much on their plate. It\'s \\"sure, and we can do more.\\"
\\n\\nIf you\'re not actively aware the design of schemas and code is tightly coupled, your codebase will explode, and the bulk of it will be glue. Much of it just serves to translate generalized intent into concrete state or commands. Arguments about schemas are usually just hidden debates about whose job it is to translate, split or join something. This isn\'t just an irrelevant matter of \\"wire formats\\" because changing the structure and format of data also changes how you address specific parts of it.
\\n\\nIn an interactive UI, you also need a reverse path, to apply edits. What I hope you are starting to realize is that this is really just the forward path in reverse, on so many levels. The result of a basic query is just the ordered IDs of the records that it matched. A join returns a tuple of record IDs per row. If you pre-assemble the associated record data for me, you actually make my job as a front-end dev harder, because there are multiple forward paths for the exact same data, in subtly different forms. What I want is to query and mutate the same damn store you do, and be told when what changes. It\'s table-stakes now.
\\n\\nWith well-architected data, this can be wired up mostly automatically, without any scaffolding. The implementations you encounter in the wild just obfuscate this, because they don\'t distinguish between the data store and the model it holds. The fact that the data store should not be corruptible, and should enforce permissions and quotas, is incorrectly extended to the entire model stored inside. But that model doesn\'t belong to Stanley, it belongs to the user. This is why desktop applications didn\'t have a \\"Data Export\\". It was just called Load and Save, and what you saved was the intent, in a file.
\\n\\nHaving a universal query or update mechanism doesn\'t absolve you from thinking about this either, which is why I think the patch
approach is so rare: it looks like cowboy coding if you don\'t have the right boundaries in place. Patch
is mainly for user-space mutations, not kernel-space, a concept that applies to more than just OS kernels. User-space must be very forgiving.
If you avoid it, you end up with something like GraphQL, a good example of solving only half the problem badly. Its getter assembles data for consumption by laboriously repeating it in dozens of partial variations. And it turns the setter part into an unsavory mix of lasagna and spaghetti. No wonder, it was designed for a platform that owns and hoards all your data.
\\n\\n* * *
\\n\\nViewed narrowly, Intent
is just a useful concept to rethink how you enforce validation and constraints in a front-end app. Viewed broadly, it completely changes how you build back-ends and data flows to support that. It will also teach you how adding new aspects to your software can reduce complexity, not increase it, if done right.
A good metric is to judge implementation choices by how many other places of the code need to care about them. If a proposed change requires adjustments literally everywhere else, it\'s probably a bad idea, unless the net effect is to remove code rather than add.
\\n\\nI believe reconcilers like React or tree-sitter are a major guide stone here. What they do is apply structure-preserving transforms on data structures, and incrementally. They actually do the annoying part for you. I based Use.GPU on the same principles, and use it to drive CPU canvases too. The tree-based structure reflects that one function\'s state just might be the next function\'s intent, all the way down. This is a compelling argument that the data and the code should have roughly the same shape.
\\n\\nYou will also conclude there is nothing more nefarious than a hard split between back-end and front-end. You know, coded by different people, where each side is only half-aware of the other\'s needs, but one sits squarely in front of the other. Well-intentioned guesses about what the other end needs will often be wrong. You will end up with data types and query models that cannot answer questions concisely and efficiently, and which must be babysat to not go stale.
\\n\\nIn the last 20 years, little has changed here in the wild. On the back-end, it still looks mostly the same. Even when modern storage solutions are deployed, people end up putting SQL- and ORM-like layers on top, because that\'s what\'s familiar. The split between back-end and database has the exact same malaise.
\\n\\nNone of this work actually helps make the app more reliable, it\'s the opposite: every new feature makes on-going development harder. Many \\"solutions\\" in this space are not solutions, they are copes. Maybe we\'re overdue for a NoSQL-revival, this time with a focus on practical schema design and mutation? SQL was designed to model administrative business processes, not live interaction. I happen to believe a front-end should sit next to the back-end, not in front of it, with only a thin proxy as a broker.
\\n\\nWhat I can tell you for sure is: it\'s so much better when intent is a first-class concept. You don\'t need nor want to treat user data as something to pussy-foot around, or handle like it\'s radioactive. You can manipulate and transport it without a care. You can build rich, comfy functionality on top. Once implemented, you may find yourself not touching your network code for a very long time. It\'s the opposite of overwhelming, it\'s lovely. You can focus on building the tools your users need.
\\n\\nThis can pave the way for more advanced concepts like OT and CRDT, but will show you that neither of them is a substitute for getting your application fundamentals right.
\\n\\nIn doing so, you reach a synthesis of Dijkstra and anti-Dijkstra: your program should be provably correct in its data flow, which means it can safely break in completely arbitrary ways.
\\n\\nBecause the I in UI meant \\"intent\\" all along.
\\n\\n\\n More:\\n
\\n\\n\\n\\n\\n\\n