Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
3e45229
feat(gnoweb): add integrated playground, eval, and fork views
moul Apr 3, 2026
6f327fb
fix(gnoweb): eval uses server-side API, fix playground layout
moul Apr 3, 2026
a7abadf
fix(gnoweb): add cache busting to dynamic JS controller imports
moul Apr 3, 2026
1cbe351
fix(gnoweb): eval form submit via direct listener and Enter key
moul Apr 3, 2026
b5643cc
fix(gnoweb): bind playground buttons directly in connect()
moul Apr 3, 2026
8e789cf
fix(gnoweb): rewrite controllers as standalone functions, add self to…
moul Apr 3, 2026
683fd5e
Merge branch 'master' into playground2
moul Apr 8, 2026
5f1febd
fix(gnoweb): fix lint errors in playground and eval controllers
moul Apr 9, 2026
6aba36f
docs(gnoweb): add ADR for integrated playground (PR #5421)
moul Apr 9, 2026
c64848f
test(gnoweb): add playground, eval, fork tests; remove dead template …
moul Apr 9, 2026
545c008
fix(gnoweb): add per-IP rate limiting to eval API, hide history until…
moul Apr 20, 2026
3958aab
feat(gnoweb): add ?run view with maketx run scratchpad
moul Apr 20, 2026
518ca8e
chore(gnoweb): improve light playground styles
jeronimoalbi Apr 20, 2026
e0c73d9
chore(gnoweb): change playground to use u-grid-full style
jeronimoalbi Apr 20, 2026
53d749b
feat(gnoweb): refactor playground render to follow coding standard
jeronimoalbi Apr 20, 2026
f42524a
chore(gnoweb): change HTTP handler static option location
jeronimoalbi Apr 20, 2026
3b93051
feat(gnoweb): change playground tabs to match menu styling
jeronimoalbi Apr 21, 2026
ad5c92e
chore(gnoweb): change gno URL to not consider package fork a playground
jeronimoalbi Apr 21, 2026
6597271
chore(gnoweb): change media breakpoints to fit new menu entries
jeronimoalbi Apr 21, 2026
45c2aed
feat: change action controller to use `/_/api/eval`
jeronimoalbi Apr 23, 2026
e16532d
fix(gnoweb): remove unused argument from `SecureHeadersMiddleware()`
jeronimoalbi Apr 24, 2026
8ababeb
feat(gnoweb): consolidate built-in playground UI (#5565)
jeronimoalbi Apr 28, 2026
6583c0d
feat(gnoweb): use CodeMirror as code editor in built-in playground (#…
jeronimoalbi May 11, 2026
126d8d7
feat(gnoweb): change built-in playgroung tabs to be scrollable (#5621)
jeronimoalbi May 11, 2026
d74438f
feat(gnoweb): compress shared built-in playground code (#5637)
jeronimoalbi May 11, 2026
b2dfda9
feat(gnoweb): move Gno expression eval support into action view (#5634)
jeronimoalbi May 11, 2026
e44f0b6
feat(gnoweb): add edit link to ".gno" source files (#5631)
jeronimoalbi May 11, 2026
ffc403d
feat(gnoweb): support multiple file download in built-in playground (…
jeronimoalbi May 11, 2026
08a8c61
Merge branch 'master' into playground2
jeronimoalbi May 11, 2026
ed52918
Merge branch 'master' into playground2
jeronimoalbi May 12, 2026
c5a4a10
chore: scroll to playground output when new output is assigned
jeronimoalbi May 14, 2026
5ebc0b1
chore: fix typing and element reference issue in playground controller
jeronimoalbi May 15, 2026
12d08db
ci: lint and make generate
jeronimoalbi May 15, 2026
d6733c5
fix: avoid TAR Slip / Path Traversal issue
jeronimoalbi May 15, 2026
b0c3e90
Merge branch 'master' into playground2
alexiscolin May 18, 2026
6e85fa4
feat(gnoweb): change built-in playground output to support copy butto…
jeronimoalbi May 18, 2026
29208c4
feat(gnoweb): change build-in playground run view to use CodeMirror (…
jeronimoalbi May 18, 2026
e476136
Merge branch 'master' into playground2
jeronimoalbi May 18, 2026
0d45ffe
chore: fix typo
jeronimoalbi May 18, 2026
3394690
chore: make generate
jeronimoalbi May 18, 2026
f89e65c
Merge branch 'master' into playground2
jeronimoalbi Jun 9, 2026
d6737e5
ci: make generate
jeronimoalbi Jun 9, 2026
9b7a602
chore: fixup
jeronimoalbi Jun 9, 2026
da815c3
chore: fixup
jeronimoalbi Jun 9, 2026
928d898
feat(gnoweb): change built-in playground and run views into features …
jeronimoalbi Jun 23, 2026
b84c63b
Merge branch 'master' into playground2
alexiscolin Jun 30, 2026
e3c08be
chore(gnoweb): regenerate frontend assets after merge
alexiscolin Jun 30, 2026
13124c5
Merge branch 'master' into playground2
moul Jul 2, 2026
c355059
Merge remote-tracking branch 'origin/master' into playground2
moul Jul 8, 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
125 changes: 125 additions & 0 deletions gno.land/adr/pr5421_integrated_playground.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
# ADR: Integrated Playground, Eval, and Fork Views in gnoweb

## Context

gno.land previously had no interactive code evaluation in gnoweb. Users who
wanted to call realm functions or experiment with Gno code had to use external
tools (gnokey CLI, a separate gnostudio/studio app, or copy-paste workflows).

The key problems:

1. **Friction for exploration.** Reading a realm's `Render` output or calling
a view function required setting up a local environment or using CLI tools.
2. **No in-browser eval.** Users could not quickly test expressions against a
deployed package from the web UI.
3. **No code scratch pad.** There was nowhere in gnoweb to write and share
short Gno snippets.
4. **Separate studio dependency.** The only interactive option required running
a separate application, breaking the single-binary model.

## Decision

Add three interactive features to gnoweb as Go-native, single-binary
extensions with no additional runtime dependencies:

### 1. `/_/play` — Playground scratch pad

A multi-file code editor backed by a plain `<textarea>` (no CodeMirror yet).

@jefft0 jefft0 Jul 2, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now out of date. PR #5674 was merged into this PR branch which implements CodeMirror. When we are ready to merge to master, we should update this ADR.

Supports:
- URL sharing via `?code=` query parameter (base64-encoded)
- Multi-file mode via `// --- filename.gno ---` separators
- Fork-from-package via `?from=` query parameter
- Tab addition/switching, keyboard shortcuts (Ctrl+Enter to run, Tab for indent)

The "Run" button currently provides useful output for packages with `Render`
by calling `/_/api/eval`. For scratch-pad code that has no on-chain package,
it prints CLI instructions instead. This is intentional: the playground is a
first step, not a full REPL.

### 2. `?eval` on realm/package pages — Expression Evaluator

Adds an "Eval" tab to the existing realm/package navigation. Renders:
- A text input for arbitrary Gno expressions
- A result pane updated via `POST /_/api/eval`
- Quick-call buttons for exported, non-crossing, non-method functions
- An expression history with re-run support

The evaluator is read-only by design: only non-crossing functions are shown in
Quick Call, and arbitrary expressions are limited to what `vm/qeval` allows
(no state mutation).

### 3. `?fork` on package/realm pages — Fork to Playground

Loads all `.gno` source files from a package via the existing `ListFiles` +
`File` client methods, concatenates them with `// --- filename.gno ---`
separators, and redirects to the playground view pre-filled with that code.

### API endpoints

Two new JSON endpoints behind `/_/api/`:

- `POST /_/api/eval` — Evaluates a `pkg_path` + `expression` pair via
`vm/qeval` ABCI query. Returns `{result}` or `{error}`.
- `GET /_/api/funcs?path=...` — Returns exported, non-crossing functions for a
package using the existing `Doc()` client method. Used by the eval quick-call
buttons and (in future) playground-aware tooling.

### Frontend approach

Vanilla TypeScript compiled to plain JS (no React, no npm at runtime). Each
feature is a standalone controller function loaded dynamically by the existing
`data-controller` dispatch mechanism in `index.ts`. No new build tooling;
compiled output is committed to `public/js/`.

Cache busting is handled by extracting the `?v=` version suffix from
`index.js`'s own `<script src>` URL and forwarding it to dynamic controller
imports.

### CSP update

`connect-src` in the Content Security Policy was extended from just the remote
ABCI endpoint to also include `'self'`, enabling the JS to call `/_/api/eval`
and `/_/api/funcs` without CSP violations.

## Alternatives Considered

- **WebSocket REPL:** More interactive but much more complex server state.
Deferred to a later iteration.
- **Server-side gno run / gno test:** Would require sandboxing, resource
limits, and execution isolation. Out of scope for this PR; instructions are
printed instead.
- **CodeMirror editor:** Better UX but adds a non-trivial JS dependency.
Deferred; the `<textarea>` approach is functional and upgradeable in place.
- **Separate gnostudio service:** Breaks the single-binary model. The goal is
to keep gnoweb self-contained.
- **Stimulus.js or other controller framework:** The codebase already has a
minimal controller dispatch in `index.ts`. Keeping new controllers as
standalone functions avoids framework coupling.

## Consequences

- **Positive:** Users can evaluate read-only realm expressions from the
browser without any local tooling.
- **Positive:** Developers can fork any package's source into the playground
with one click.
- **Positive:** Zero new runtime dependencies; gnoweb stays a single binary.
- **Positive:** The `/_/api/eval` and `/_/api/funcs` endpoints form a stable
base for future tooling (IDE integrations, CLI helpers, etc.).
- **Trade-off:** The playground cannot execute scratch-pad code that isn't
deployed on-chain. This is acceptable for an initial iteration; the UI
prints CLI instructions to bridge the gap.
- **Trade-off:** No rate-limiting or sandboxing on `/_/api/eval`. Acceptable
for a dev/exploration tool; should be addressed before exposing this to
mainnet at scale.
- **Trade-off:** Compiled JS is committed to the repo. This is consistent with
the existing gnoweb frontend approach.

## Not Yet Implemented

- CodeMirror syntax highlighting and editor features
- Server-side `gno run` / `gno test` execution with sandboxing
- WebSocket REPL
- Wallet integration (signing transactions from playground)
- gnodev hot-reload integration
- Rate limiting / abuse prevention on eval API
- Full test coverage for playground handler (current patch coverage ~16%)
3 changes: 2 additions & 1 deletion gno.land/cmd/gnoweb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ Environment variables:
}

return run()
})
},
)

cmd.Execute(context.Background(), os.Args[1:])
}
Expand Down
7 changes: 3 additions & 4 deletions gno.land/cmd/gnoweb/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,10 +107,9 @@ func TestSecureHeadersMiddlewareStrict(t *testing.T) {
if !strings.Contains(csp, "https://assets.gnoteam.com") {
t.Errorf("Expected Content-Security-Policy to contain 'https://assets.gnoteam.com', got '%s'", csp)
}
// The search dropdown fetches the same-origin /search.json, so connect-src
// must allow 'self' in addition to the RPC node.
if !strings.Contains(csp, "connect-src 'self' http://example.com/abci_query") {
t.Errorf("Expected Content-Security-Policy connect-src to contain 'self' and the RPC node, got '%s'", csp)
// The search dropdown fetches the same-origin /search.json, so connect-src must allow 'self'.
if !strings.Contains(csp, "connect-src 'self'") {
t.Errorf("Expected Content-Security-Policy connect-src to contain 'self', got '%s'", csp)
}
if res.Header.Get("Strict-Transport-Security") != "max-age=31536000" {
t.Errorf("Expected Strict-Transport-Security 'max-age=31536000', got '%s'", res.Header.Get("Strict-Transport-Security"))
Expand Down
20 changes: 13 additions & 7 deletions gno.land/pkg/gnoweb/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ biome := $(node_modules)/.bin/biome
css_config_path := frontend/css/main.css

# main css config — includes per-feature CSS pulled into the Cube CSS Block
# layer via @import from frontend/css/06-blocks.css (postcss-import resolves
# at compile time, so the final bundle and layer order are unchanged).
# layer via @import from frontend/css/main.css (postcss-import resolves at
# compile time, so the final bundle and layer order are unchanged).
main_css_files := $(shell find frontend/css feature -name '*.css' 2>/dev/null)
output_maincss := $(PUBLIC_DIR)/main.css

Expand Down Expand Up @@ -53,10 +53,16 @@ shared_js := $(src_dir_js)/controller.ts
controller_js := $(filter-out $(shared_js),$(input_js))
# Flatten: any .ts (legacy or feature) → $(out_dir_js)/<basename>.js.
output_js := $(addprefix $(out_dir_js)/,$(patsubst %.ts,%.js,$(notdir $(input_js))))
# Tell make where to look for .ts sources so the %.js pattern rule
# below resolves both legacy and feature-local controllers.
# Tell make where to look for .ts sources so the %.js pattern rule below
# resolves both legacy and feature-local controllers.
vpath %.ts $(src_dir_js) $(shell find feature -path 'feature/*/frontend' -type d 2>/dev/null)

# Alias so controllers outside frontend/js/ (per-feature controllers under
# feature/<name>/frontend/) can import shared modules as @gnoweb/js/... instead
# of brittle ../../../frontend/js/ relative paths. Resolved relative to esbuild's
# working directory (this Makefile's dir).
esbuild_alias := --alias:@gnoweb/js=./frontend/js

# cache
cache_dir := .cache

Expand Down Expand Up @@ -106,7 +112,7 @@ node_path := $(node_modules)
ts: $(esbuild) $(output_js)
# Build shared chunk first (always loaded)
$(out_dir_js)/controller.js: $(shared_js) $(esbuild)
NODE_ENV=production NODE_PATH=$(node_path) $(esbuild) $< --log-level=error --bundle --outfile=$@ --format=esm --minify
NODE_ENV=production NODE_PATH=$(node_path) $(esbuild) $< --log-level=error --bundle --outfile=$@ --format=esm --minify $(esbuild_alias)

# sa-bootstrap is loaded as a synchronous classic <script src=...> so it blocks
# parsing until executed and guarantees window.sa_metadata is set before SA's
Expand All @@ -118,7 +124,7 @@ $(out_dir_js)/sa-bootstrap.js: $(src_dir_js)/sa-bootstrap.ts $(esbuild)
# prerequisite lets `vpath` resolve sources from frontend/js/ OR from
# feature/<name>/frontend/ (set above). $< is the path Make found.
$(out_dir_js)/%.js: %.ts $(out_dir_js)/controller.js
NODE_ENV=production NODE_PATH=$(node_path) $(esbuild) $< --log-level=error --bundle --outdir=$(out_dir_js) --format=esm --define:process.env.NODE_ENV="\"production\"" --minify --external:./controller.js
NODE_ENV=production NODE_PATH=$(node_path) $(esbuild) $< --log-level=error --bundle --outdir=$(out_dir_js) --format=esm --define:process.env.NODE_ENV="\"production\"" --minify --external:./controller.js $(esbuild_alias)

# Rule to copy static files while preserving directory structure
static: $(output_static)
Expand Down Expand Up @@ -152,7 +158,7 @@ dev.maincss: generate | $(PUBLIC_DIR)

# TS in development mode
dev.ts: $(esbuild) generate | $(PUBLIC_DIR)
NODE_ENV=development NODE_PATH=$(node_path) $(esbuild) $(input_js) --bundle --outdir=$(out_dir_js) --sourcemap --format=esm --watch \
NODE_ENV=development NODE_PATH=$(node_path) $(esbuild) $(input_js) --bundle --outdir=$(out_dir_js) --sourcemap --format=esm --watch $(esbuild_alias) \
2>&1 | $(logname) esbuild

# Install node modules deps located in ./tools
Expand Down
4 changes: 4 additions & 0 deletions gno.land/pkg/gnoweb/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,10 @@ func NewRouter(logger *slog.Logger, cfg *AppConfig) (http.Handler, error) {
assetsHandler := cacheAssetHandler(AssetHandler())
mux.Handle(assetsBase, http.StripPrefix(assetsBase, assetsHandler))

// Handle playground API endpoints

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines wire playground endpoints directly into the root mux. I'd like to suggest reshaping the playground as a self-contained module under pkg/gnoweb/feature/playground/, matching what the state-explorer
refactor takes in #5649. Same shape, applied to playground:

  pkg/gnoweb/feature/playground/
  ├── feature.go            // entry / Register(mux, deps)
  ├── handler.go            // GetPlaygroundView, GetForkView, GetRunView (today in handler_http.go:779-868)
  ├── handler_api.go        // serveEval, serveFuncs (today in handler_playground.go)
  ├── ratelimit.go          // playgroundRateLimiter (today in handler_playground.go)
  ├── validate.go           // pkg_path sanitization, base64+deflate decode helpers
  ├── client.go             // local subset interface (Eval, Doc, ListFiles, File)
  ├── templates/playground.html, run.html
  ├── frontend/controller-playground.ts, controller-run.ts, controller-action-eval.ts
  ├── frontend/playground.css   // own file, not 594 lines appended to 06-blocks.css (now 3163 lines)
  └── *_test.go

This is a transitional shape (same as I did for #5649 for state-explorer this week). The final form is a feature framework: a featureapi/ leaf package with capability interfaces (QueryHandler for ?eval/?fork/?run, RouteProvider for //play + //api/*, LayoutContributor for the Eval/Fork header nav, Middlewarer for rate limiting). Playground would actually be the densest user of the framework: it implements all four. I'll open an issue with the full design ASAP so we have a proper place to discuss it but it covers anti-amplification, dispatch order, frontend build pipeline, and explains why this intermediate module shape is the right next step.

Why it matters specifically here, beyond cleanup IMO:

  • Anti-amplification is baked into the framework: each interaction = one fetch = at most one upstream RPC. Today's GetForkView doing N serial RPCs is exactly the kind of thing the framework rules out. Better to move toward that shape now than retrofit later.
  • Per-feature middleware is where rate limiting / body caps / pkg_path validation naturally live: instead of handler_playground.go reimplementing security primitives.
  • Eval method stays out of the root ClientAdapter (today added at client.go): local subset interface avoids growing the root interface per feature.
  • Templates colocated with handler: easier to grep, to test and to scope, easier to check and delete the whole feature as a unit.

@jeronimoalbi jeronimoalbi Jun 4, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved playground and run functionality into features in PR #5774 using patterns from #5649. This is a first step, before the final form you mention is defined.

Maybe other pending review changes could be addressed after merging it, because some file had to be splitted into new files. Addressing review changed could be simpler that way 🤔

mux.Handle("/_/api/eval", httphandler.Playground.EvalHandler())
mux.Handle("/_/api/funcs", httphandler.Playground.FuncsHandler())

// Handle status page
mux.Handle("/status.json", handlerStatusJSON(logger, rpcclient))

Expand Down
10 changes: 10 additions & 0 deletions gno.land/pkg/gnoweb/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ type ClientAdapter interface {
// any positive value pins the query to that historical height.
Doc(ctx context.Context, path string, height int64) (*doc.JSONDocumentation, error)

// Eval evaluates a Gno expression via vm/qeval query.
// The data string should be in the format "gno.land/r/pkg.Expression(args)".
Eval(ctx context.Context, data string) ([]byte, error)

// StatePkg retrieves the root state tree for a package. `height
// = 0` queries the latest block; positive values are forwarded
// to ABCI as-is.
Expand Down Expand Up @@ -233,6 +237,12 @@ func (c *rpcClient) Doc(ctx context.Context, pkgPath string, height int64) (*doc
return jdoc, nil
}

// Eval evaluates a Gno expression via the vm/qeval ABCI query.
func (c *rpcClient) Eval(ctx context.Context, data string) ([]byte, error) {
const qpath = "vm/qeval"
return c.query(ctx, qpath, []byte(data), 0)
}

// StatePkg retrieves root state tree for a package via vm/qpkg_json.
// `height = 0` queries the latest block.
func (c *rpcClient) StatePkg(ctx context.Context, path string, height int64) ([]byte, error) {
Expand Down
7 changes: 7 additions & 0 deletions gno.land/pkg/gnoweb/client_mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
type MockPackage struct {
Path string
Domain string
Remote string
ChainId string
Files map[string]string // filename -> body
Functions []*doc.JSONFunc
}
Expand Down Expand Up @@ -138,6 +140,11 @@ func (m *MockClient) Doc(ctx context.Context, path string, _ int64) (*doc.JSONDo
return &doc.JSONDocumentation{Funcs: pkg.Functions}, nil
}

// Eval evaluates a Gno expression (mock implementation).
func (m *MockClient) Eval(_ context.Context, data string) ([]byte, error) {
return []byte("(mock eval: " + data + ")"), nil
}

// StatePkg returns mock package state data for testing.
func (m *MockClient) StatePkg(_ context.Context, path string, _ int64) ([]byte, error) {
_, exists := m.Packages[path]
Expand Down
30 changes: 26 additions & 4 deletions gno.land/pkg/gnoweb/components/layout_header.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,12 @@ func StaticHeaderGeneralLinks() []HeaderLink {
}

func StaticHeaderDevLinks(u weburl.GnoURL, mode ViewMode, static bool) []HeaderLink {
contentURL, sourceURL, helpURL, stateURL := u, u, u, u
contentURL, sourceURL, helpURL, forkURL, runURL, stateURL := u, u, u, u, u, u
contentURL.WebQuery = url.Values{}
sourceURL.WebQuery = url.Values{"source": {""}}
helpURL.WebQuery = url.Values{"help": {""}}
forkURL.WebQuery = url.Values{"fork": {""}}
runURL.WebQuery = url.Values{"run": {""}}
stateURL.WebQuery = url.Values{"state": {""}}

contentLink := HeaderLink{
Expand All @@ -69,6 +71,20 @@ func StaticHeaderDevLinks(u weburl.GnoURL, mode ViewMode, static bool) []HeaderL
IsActive: isActive(u.WebQuery, "Actions"),
}

forkLink := HeaderLink{
Label: "Fork",
URL: forkURL.EncodeWebURL(),
Icon: "ico-link",
IsActive: isActive(u.WebQuery, "Fork"),
}

runLink := HeaderLink{
Label: "Run",
URL: runURL.EncodeWebURL(),
Icon: "ico-tx-link",
IsActive: isActive(u.WebQuery, "Run"),
}

stateLink := HeaderLink{
Label: "State",
URL: stateURL.EncodeWebURL(),
Expand All @@ -84,9 +100,11 @@ func StaticHeaderDevLinks(u weburl.GnoURL, mode ViewMode, static bool) []HeaderL
case mode == ViewModeUser:
return []HeaderLink{contentLink}
case mode == ViewModePackage:
return []HeaderLink{contentLink, sourceLink}
return []HeaderLink{contentLink, sourceLink, forkLink}
case mode == ViewModePlayground:
return []HeaderLink{}
default:
return []HeaderLink{contentLink, stateLink, sourceLink, actionsLink}
return []HeaderLink{contentLink, stateLink, sourceLink, actionsLink, forkLink, runLink}
}
}

Expand All @@ -105,13 +123,17 @@ func EnrichHeaderData(data HeaderData, mode ViewMode) HeaderData {
func isActive(webQuery url.Values, label string) bool {
switch label {
case "Content":
return !webQuery.Has("source") && !webQuery.Has("help") && !webQuery.Has("state")
return !webQuery.Has("source") && !webQuery.Has("help") && !webQuery.Has("fork") && !webQuery.Has("run") && !webQuery.Has("state")
case "State":
return webQuery.Has("state")
case "Source":
return webQuery.Has("source")
case "Actions":
return webQuery.Has("help")
case "Fork":
return webQuery.Has("fork")
case "Run":
return webQuery.Has("run")
default:
return false
}
Expand Down
24 changes: 13 additions & 11 deletions gno.land/pkg/gnoweb/components/layout_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,19 +17,21 @@ import (
type ViewMode int

const (
ViewModeExplorer ViewMode = iota // For exploring packages and paths
ViewModeRealm // For realm content display
ViewModePackage // For package content display
ViewModeHome // For home page display
ViewModeUser // For user page display
ViewModeExplorer ViewMode = iota // For exploring packages and paths
ViewModeRealm // For realm content display
ViewModePackage // For package content display
ViewModeHome // For home page display
ViewModeUser // For user page display
ViewModePlayground // For playground display
)

// View mode predicates
func (m ViewMode) IsExplorer() bool { return m == ViewModeExplorer }
func (m ViewMode) IsRealm() bool { return m == ViewModeRealm }
func (m ViewMode) IsPackage() bool { return m == ViewModePackage }
func (m ViewMode) IsUser() bool { return m == ViewModeUser }
func (m ViewMode) IsHome() bool { return m == ViewModeHome }
func (m ViewMode) IsExplorer() bool { return m == ViewModeExplorer }
func (m ViewMode) IsRealm() bool { return m == ViewModeRealm }
func (m ViewMode) IsPackage() bool { return m == ViewModePackage }
func (m ViewMode) IsUser() bool { return m == ViewModeUser }
func (m ViewMode) IsHome() bool { return m == ViewModeHome }
func (m ViewMode) IsPlayground() bool { return m == ViewModePlayground }

// ShouldShowDevTools returns whether dev tools should be shown for this mode
func (m ViewMode) ShouldShowDevTools() bool {
Expand Down Expand Up @@ -196,7 +198,7 @@ func IndexLayout(data IndexData) Component {

// Set dev mode based on view type and mode
switch data.BodyView.Type {
case HelpViewType, SourceViewType, DirectoryViewType, StatusViewType, StateViewType:
case HelpViewType, SourceViewType, DirectoryViewType, StatusViewType, StateViewType, PlaygroundViewType, RunViewType:
dataLayout.IsDevmodView = true
}

Expand Down
Loading