Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
a861eff
feat: add first github pages playground
thomas9911 Apr 12, 2026
aaadd55
chore: add `playground/` directory to excluded paths
micahkepe Apr 12, 2026
3f54649
chore: use same compile flags as main crate
micahkepe Apr 12, 2026
6feee8a
fix(script): `bun dev` doesn't work on clean checkout
micahkepe Apr 12, 2026
2a4c004
chore: remove dev scripts
micahkepe Apr 13, 2026
da0242c
ci(playground): only deploy on changes to playground files, use legac…
micahkepe Apr 13, 2026
18c4bcd
chore: update playground README
micahkepe Apr 13, 2026
2da1f20
chore: use `-rf` to not exit on non-existent directory (first run)
micahkepe Apr 13, 2026
c08d728
refactor(playground): create common `run_query` function, fix `None` …
micahkepe Apr 13, 2026
ea67970
chore: remove misleading comment
micahkepe Apr 13, 2026
01ef068
feat(playground): first pass of refactoring playground UI
micahkepe Apr 13, 2026
4590097
feat(playground): add light/dark toggle
micahkepe Apr 13, 2026
330e70a
fix(playground): auto-create gh-pages branch on fresh checkout
micahkepe Apr 13, 2026
a30694e
feat(playground): add border accent flash for shortcut visual feedback
micahkepe Apr 13, 2026
68343b7
chore: run formatting, add background color to inputs and results
micahkepe Apr 13, 2026
a83b110
feat(playground): global mod + enter submit bind, fixed width theme
micahkepe Apr 13, 2026
44c63ac
feat(playground): add help overlay (? toggle)
micahkepe Apr 13, 2026
eb4d1b5
fix(playground): show root value on empty query to match CLI
micahkepe Apr 13, 2026
9e6fc7c
fix(playground): show "no matches" on no results
micahkepe Apr 13, 2026
7a3e95d
feat(playground): global ESC to lose focus on current input
micahkepe Apr 13, 2026
a4d4cfe
feat(playground): link to main repo
micahkepe Apr 13, 2026
a53e9d9
fix(playground): no layout shift with many results
micahkepe Apr 13, 2026
4014992
feat(playground): URL shareable hashes, query examples
micahkepe Apr 13, 2026
d5fe0b5
test(playground): add unit tests verifying behavior
micahkepe Apr 13, 2026
7bdcd2e
chore(playground): add installation link
micahkepe Apr 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .github/workflows/deploy-playground.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
name: Deploy playground to Pages

on:
# Runs on pushes targeting the default branch
push:
branches: [main]

# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:

# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
pages: write
id-token: write

# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
concurrency:
group: "pages"
cancel-in-progress: false

jobs:
# Build job
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: bun-${{ runner.os }}-${{ hashFiles('**/bun.lock') }}
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: wasm32-wasip2
cache: true
cache-directories: playground/wasm/target
- name: Install & Build
working-directory: playground
run: |
bun install
bun run build
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: ./playground/dist

# Deployment job
deploy:
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
needs: build
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ exclude = [
"benches/",
"tests/",
"justfile",
".gitmodules"
".gitmodules",
"playground"
]

[dependencies]
Expand Down
37 changes: 37 additions & 0 deletions playground/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# dependencies (bun install)
node_modules

# output
out
dist
*.tgz

# code coverage
coverage
*.lcov

# logs
logs
_.log
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# caches
.eslintcache
.cache
*.tsbuildinfo

# IntelliJ based IDEs
.idea

# Finder (MacOS) folder config
.DS_Store

# generated from jco transpile
generated
21 changes: 21 additions & 0 deletions playground/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# bun-react-template

To install dependencies:

```bash
bun install
```

To start a development server:

```bash
bun dev
```

To run for production:

```bash
bun start
```

This project was created using `bun init` in bun v1.3.1. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
17 changes: 17 additions & 0 deletions playground/bun-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Generated by `bun init`

declare module "*.svg" {
/**
* A path to the SVG file
*/
const path: `${string}.svg`;
export = path;
}

declare module "*.module.css" {
/**
* A record of class names to their corresponding CSS module classes
*/
const classes: { readonly [key: string]: string };
export = classes;
}
172 changes: 172 additions & 0 deletions playground/bun.lock

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions playground/bunfig.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[serve.static]
env = "BUN_PUBLIC_*"
29 changes: 29 additions & 0 deletions playground/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
{
"name": "jsongrep-playground",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"setup": "bun run wasm:build && bun run wasm:transpile",
"dev": "bun run setup && bun --hot src/index.ts",
"build": "bun run setup && bun build ./src/index.html --outdir=dist --sourcemap --target=browser --minify --define:process.env.NODE_ENV='\"production\"' --env='BUN_PUBLIC_*' && bun run wasm:move",
"start": "NODE_ENV=production bun src/index.ts",
"clean": "rm -r generated dist",
"wasm:build": "cd wasm && cargo build --release --target wasm32-wasip2",
"wasm:sanity-check": "bun run wasm:build && cd wasm && sh ./sanity-check.sh",
"wasm:move": "cp generated/*.wasm dist",
"wasm:transpile": "bun run wasm:jco:types && bun run wasm:jco:transpile",
"wasm:jco:transpile": "bun run jco transpile ./wasm/target/wasm32-wasip2/release/jsongrep_wasm.wasm -o generated",
"wasm:jco:types": "rm -rf generated && bun run jco types -o generated ./wasm/wit"
},
"dependencies": {
"@bytecodealliance/jco": "^1.17.6",
"react": "^19",
"react-dom": "^19"
},
"devDependencies": {
"@types/react": "^19",
"@types/react-dom": "^19",
"@types/bun": "latest"
}
}
13 changes: 13 additions & 0 deletions playground/src/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Playground } from "./Playground";
import "./index.css";

export function App() {
return (
<div className="app">
<h1>JSONGrep</h1>
<Playground />
</div>
);
}

export default App;
105 changes: 105 additions & 0 deletions playground/src/Playground.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState, type FormEvent } from "react";
import { useId } from "react";
import { jsongrep } from "generated/jsongrep_wasm";

export function Playground() {
const [data, setData] = useState("");
const [query, setQuery] = useState("");
const [output, setOutput] = useState("");
const [compileTiming, setCompileTiming] = useState("0");
const [queryTiming, setQueryTiming] = useState("0");

const queryId = useId();
const dataId = useId();
const outputId = useId();

const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();

if (!data || !query) {
setOutput("Please provide both data (JSON/YAML) and a query.");
return;
}

try {
const beforeRoundtrip = performance.now();
const resultsWithTimings: jsongrep.TimingResults =
jsongrep.queryWithTimings(data, query);
const afterRoundtrip = performance.now();
const roundtrip = afterRoundtrip - beforeRoundtrip;

if (localStorage.getItem("JSONGREP_TIMINGS")) {
console.log({ timings: resultsWithTimings.timings, roundtrip });
}
const results: string[] = resultsWithTimings.results.map(
([, value]) => value,
);
setOutput(
results.length > 0
? results.join("\n\n---\n\n")
: "No results found matching the query.",
);
resultsWithTimings.timings.compileNs === 0n
? setCompileTiming("< 1")
: setCompileTiming(
`${Number(resultsWithTimings.timings.compileNs) / 1e6}`,
);
resultsWithTimings.timings.queryNs === 0n
? setQueryTiming("< 1")
: setQueryTiming(`${Number(resultsWithTimings.timings.queryNs) / 1e6}`);
} catch (error) {
let message = "An unknown error occurred.";
if (typeof error === "string") message = error;
else if (error instanceof Error) message = error.message;
setOutput(`ERROR: ${message}`);
}
};

return (
<div className="api-tester">
<div className="inputs-panel">
<div className="input-group">
<label htmlFor={dataId}>Data (JSON / YAML)</label>
<textarea
id={dataId}
value={data}
onChange={(e) => setData(e.target.value)}
placeholder="Paste your JSON or YAML data here."
className="textarea data-box"
/>
</div>

<div className="input-group">
<label htmlFor={queryId}>Query</label>
<textarea
id={queryId}
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="e.g. users[*].name"
className="textarea query-box"
/>
</div>

<form onSubmit={handleSubmit}>
<button type="submit" className="run-button">
Run Query
</button>
</form>
</div>

<div className="output-panel">
<label htmlFor={outputId}>Results</label>
<textarea
id={outputId}
value={output}
readOnly
placeholder="Results will appear here…"
className="textarea output-box"
/>
</div>
<div>
compile query: {compileTiming} ms and run query: {queryTiming} ms
</div>
</div>
);
}
26 changes: 26 additions & 0 deletions playground/src/frontend.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* This file is the entry point for the React app, it sets up the root
* element and renders the App component to the DOM.
*
* It is included in `src/index.html`.
*/

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";

const elem = document.getElementById("root")!;
const app = (
<StrictMode>
<App />
</StrictMode>
);

if (import.meta.hot) {
// With hot module reloading, `import.meta.hot.data` is persisted.
const root = (import.meta.hot.data.root ??= createRoot(elem));
root.render(app);
} else {
// The hot module reloading API is not available in production.
createRoot(elem).render(app);
}
Loading