Pangram verdict · v3.3
We believe that this text is a mix of AI and human-written content.
AI likelihood · overall
MixedArticle text · 1,658 words · 29 segments analyzed
Like a lot of companies, we pay for Looker. Let’s say $150k a year, the average Looker licence1, roughly 300 seats. We are 3 data platform engineers at a company of about 500 people, and most of our week goes on the unglamorous stuff: keeping Snowflake fed, babysitting a dbt project that passed 2,000 models a while ago, and running the Airflow, Airbyte, DataHub and Kubernetes plumbing underneath it all. In February, one of us built a thing on the side to host HTML files.
It took an afternoon.
8 months later, and it’s already got as many weekly users as Looker in our company, maybe even more and Looker’s down a third. Tech influencers keep posting about software being dead, and some say they’ve cancelled and vibecoded all their SaaS subscriptions. They say AI is going to kill B2B SaaS.2 SaaS is dead, apparently, so can we replace our enterprise BI tool? Share of "self-hosted" GitHub repos Meanwhile, "slop" on Hacker News Left: share of new GitHub repos described as "self-hosted". Right: share of Hacker News comments saying "slop".3 The slop got good A year ago I thought most AI output was slop, and most of it was. We made a few “chat with your data” demos, and they looked great on a toy schema with 4 tables, but never really worked at our scale.
The SQL ran without errors, but the numbers were wrong. That’s much worse.
Then Claude Code with Opus 4.5 got really good. Like, really good. In early January adoption across the company skyrocketed. It could work in our repos and use our tools. Also, it could run a query to check the numbers before answering.
That mattered a lot more than getting a clever-sounding response.
Within a few weeks, people in finance, ops and marketing who had never opened a terminal were prompting all day. All of a sudden everyone was prompting, and nobody was clicking. Around this time, Claude started spitting out a lot of HTML. You would ask for an analysis and you’d get a single file with charts, and honestly, some of those pages looked better than our Looker dashboards. The problem was that it would live in someone’s Downloads folder. To share it, they would attach it in Slack and hope. This was way before Anthropic shipped shareable artifacts for Team and Enterprise (June 2026), and we really needed a way to share this new kind of dashboard. One day Maddy from our team said she’d just put them all on a web server, with an internal domain name. By the end of the day she had vibe-coded the whole thing: a small service that stored the files in S3, SSO in front of it, and (some) tests. We already run everything on Kubernetes, so deploying a service was mostly a matter of adding a Terraform module and a Helm chart.
Having all that plumbing in place made the whole thing much easier.
She wrote a small Python script and packaged it in a Claude plugin. Users would get their Claude to write the HTML, the script would push it (behind SSO), and anyone in the company could open the link. Initially, the files were just plain, static HTML. In March we thought, why not, and added a live query API using the viewer’s Snowflake OAuth token: best decision of the year. Turns out, we didn’t need to write a permission model.
Snowflake already had one all along.
It had roles we’d been maintaining for years, and if Snowflake says you can’t read the sales table, then neither can the dashboard.
By April, Snowflake OAuth was the only login.
People loved it, for a reason that's embarrassing for the BI industry: the dashboards actually looked good. A Looker tile is a box with a fixed list of chart types and a fixed set of colours, and it looks like every other Looker tile ever made.
Ugly, and no personality.
Fixed-tile, ugly Looker dashboard Free-form LLM-generated HTML in less than 1 minute By getting rid of the rigid fixed tiles, our users could produce free-form HTML and prompt it until it looked neat.
They loved it.
Email and Slack exports came in April, parameters in May, and weekly viewers went from 23 in March to 136 in June.
The exec team started to send links around. People also made the tool their own.
They found use cases we would never have thought of, set dashboards to regenerate themselves on a schedule, added buttons, and built some wild designs and UX that no BI tool would have let them.
Security, at this point, was bad.
Like, really bad.
Dashboard JavaScript ran on the same origin as your session, nothing had permissions, and every employee could read every dashboard. It was basically one big XSS hole: any dashboard could run code as whoever opened it and pull data out in their name.
We knew, and we shipped it anyway, because it was internal, behind VPN and SSO, and nothing else did this job.
You"make me adashboard of X"The data-viz skillour templates, chart rules, how to publishUSESClaudelooks at the data,writes the pageOne HTML filecharts + JS + the SQLOur platformhosts the page,runs the queriesA colleagueopens the linkThe dashboardruns in their browserSnowflakeruns the SQL as themASKSWRITESPUBLISHOPENS"GIVE ME THE DATA"AS THE VIEWERROWSClaude writes the page, the platform hosts it, queries run as the viewer.
OK, let’s do it properly In our team, “let’s just build a BI tool” became a running joke. It stayed a joke for a few months, until it became clear we were in too deep.
We had piled months of vibe-coding onto something with a security model we weren’t proud of, and half the company was using it. We stopped joking and said it properly: let’s build a BI tool. We got in front of a whiteboard, mostly to work out how to fix the security model, and to agree on what we didn’t want to lose: the same S3 bucket, no migration, nothing breaking for the people already using it, and publishing that stayed as easy as before. The one thing everyone agreed on: anyone can still push any HTML in one prompt, with no review step, no template and no list of approved widgets. We’d still defend that. Every constraint you put between people and their dashboard sends them back to the Downloads folder. The governance that matters already happens upstream, in dbt models and Snowflake roles. So the dashboard itself can stay as loose, as fast and as good-looking as people want.
There were 4 problems to solve.
One connector instead of a script. The upload script had been copied into 2 repos and a plugin, and every copy was a bit different. We replaced all of them with one remote MCP connector for the whole company, so claude.ai, Claude Code, Claude in Slack and our own analyst agent all call the same tools. The connector passes your Snowflake token along, and the API checks who you are with SELECT CURRENT_USER(). Whatever an AI uploads lands as a draft, and a human has to click Publish. Anyone"make me adashboard of X"Claudechat · Code · Slack· agents · CIConnectorone MCP serverfor the whole orgAPIdraft → you click PublishS3filesPostgresversions, grantsSnowflakeknows who you areASKSMCPYOUR TOKENFILESMETADATAWHO ARE YOU?Claude writes the dashboard; the person who asked owns it, under their own Snowflake identity. Assume every dashboard is hostile. This is the part we needed to actually think about. The whole product is basically XSS by design: anyone can upload JavaScript that runs in a colleague’s browser, nobody reviews it, and the colleague might be the CFO. We can’t block the XSS, because the XSS is the product, so our job is to make sure it can’t reach anything. Our security model starts with “We assume it is hostile” for that reason. Each dashboard runs in a sandboxed iframe on its own origin, which gets no cookies and a strict CSP. The only thing it can talk to is the parent page, through postMessage, and the parent checks who sent the message, attaches the session and calls the API. Content tokens last an hour and work for one dashboard, one viewer and one host. your colleague's browserSANDBOXThe dashboardwritten by Claude,trusted by no oneno cookies · strict CSPThe shellholds your session,checks who is askingAPImay you seethis dashboard?Snowflakeruns the SQL as you,your roles decidePOSTMESSAGEROWS+ YOUR SESSIONAS YOUDashboard code never touches a credential, and every query runs as the person looking at it. The sandbox protects the browser. We also created a separate metadata service that reads every new version of a dashboard async. It pulls out the whole HTML and the Snowflake queries. It checks for the scripts the page loads and where it sends data. Then a set of rules judges those facts. Most rules only report. They flag a dashboard that reads raw data instead of a dbt model, a script that isn’t pinned to a version, or a dashboard nobody has opened in 90 days. Some rules can block a dashboard: a script from a package nobody here has used before, or a WebRTC connection, which is the one way out that CSP can’t block. A blocked dashboard doesn’t load. The viewer sees a warning and has to accept it before the page runs. APInew versionpublishedQueueMetadata serviceWHAT IT DOES· tables it queries· scripts it loads· where it sends datastored once per versionEVENTRULES!Reporta flag the author can see✕Blockviewer gets a warningbefore the page runs✓Reviewedchecked by the platform,not by a persononce a day: has anyone opened it in 90 days?Checks run after publish, so nobody waits for them. Let Snowflake do identity and permissions. Every query runs with the viewer’s own token. Sharing a dashboard never shares data access. That’s a security feature, and it’s also a laziness feature: we never had to build or maintain data permissions, because everyone who logs in already has a Snowflake user with the right roles.
Content management.