[pull] master from GaijinEntertainment:master#991
Merged
Conversation
Lands the new "Forge" design across the daslang.io site and replaces three mismatched highlighters with one CodeMirror mode driven by the canonical lexer keyword set. Site: - Landing rewrite (forge.css/forge.js/index.html): Forge tokens, hero terminal panel with three live samples, dasProfile bench renderer, install tabs, news feed, ecosystem footer. - /playground/ skinned in Forge (forge-skin.css over web/ui's main.css). - Blog: 25 posts migrated from borisbat/dascf-blog with Hexo-style front matter, build_blog.py renders HTML + RSS + changelist. - /news/ from _news/*.md (27 entries, oldest 2019 -> newest 2026-05-09). - /downloads.html for binary releases. Highlighters unified: - Vendored CodeMirror 5.65.16 + simple-mode addon under site/files/cm/. - Single keyword module (daslang-keywords.js) sourced from ds2_lexer.lpp (105 reserved words); used by both daslang-mode.js (CodeMirror) and highlight.js (blog tokenizer). - cm-forge.css theme maps CM token classes to Forge tokens; replaces the eclipse theme + JS-heuristic mode previously used by /playground/. - Hero editor is now a CodeMirror instance (drops ~150 lines of contenteditable wiring); "playground" button trampolines current source into /playground/ via #code= URL hash. - Pygments daslang lexer in doc/source/daslang.py refreshed: removed move/move_new/nothing (no longer real), added capture/static_elif/template/const/default/typedecl/uninitialized. Sphinx docs reskinned via doc/source/_static/custom.css + forge-logo. CI (.github/workflows/pages.yml): builds WASM via Emscripten, copies site/files (including cm/ bundle), renders blog + news, snapshots dasProfile JSON, drops everything into the Pages artifact.
Phase 1 of the playground multi-file plan: lock the test harness shape before the real features land. No production code touched. - site/tests/playground/ — Playwright config + fixtures + one smoke spec asserting the page loads with the Forge-themed CodeMirror instance and the daslang mode is tokenizing source. - .github/workflows/playground-e2e.yml — PR + branch-push CI gate. Builds the site without WASM (skipping the 5-10 min Emscripten step), boots python -m http.server against _site/, runs the suite minus any spec tagged @wasm. Uploads playwright-report/ on failure. - site/.gitignore — tests/playground/{node_modules,playwright-report, test-results,blob-report}/ - .gitignore — .playwright-mcp/ scratch dir from the local MCP Subsequent phases will land their specs alongside their features (dropdowns, tabs, share button, hero handoff).
Phase 2: the Examples + Tests <select>s were populated correctly but
never had a change handler, so picking an option did nothing. selectSample
itself had a latent bug — `vv !== NaN` is always true, so any non-numeric
value would crash on the lookup.
- Add `onchange="selectSample('examples'|'tests')"` to both <select>s.
- Fix the NaN check (`!Number.isNaN`) and validate the index range up
front; bail cleanly on the "Select..." sentinel.
- Refactor selectSample to load every entry of `files[]` in parallel
(samples have always shipped a multi-file schema even though loaders
only ever read files[0]). The bundle is handed to a new `loadSample`
helper that surfaces main.das (or the first file) in the single CM
instance and stashes the full bundle on window for phase 3's tab strip
to pick up.
- web/ui/src/main.js is the canonical source — site/playground/main.js
is a gitignored CI re-sync, so the patch goes upstream.
- site/tests/playground/dropdowns.spec.js asserts both dropdowns swap
the editor buffer and the sentinel-reset behavior holds.
Phase 3: the playground now edits multiple files. CodeMirror.Doc per file,
editor.swapDoc on tab click, full add/delete/rename UI, localStorage
autosave, URL-hash override.
Tab strip (replaces the hardcoded main.das label in the toolbar):
- Click tab → switch active buffer (in-place class toggle to avoid
destroying the DOM mid-dblclick).
- Click + → adds untitled{N}.das, switches to it.
- Double-click name → inline rename input; Enter commits, Escape cancels.
- Click × → confirm if non-empty, delete, fall back to main.das.
- main.das is fixed: × disabled, rename rejected, delete refused.
Multi-file run (main.js): runCode iterates every pgState file via
FS.writeFile before Module.callMain('main.das'). Same MEMFS, same
require resolution, just N files instead of one.
URL hash + autosave priority:
- `#code=<percent>` (hero handoff) → wrapped into main.das.
- `#z=<lz-base64>` (share button — phase 6) → full multi-file payload.
- localStorage `daslang.playground.state.v1` autosaves debounced (250ms),
restored only when no hash is present.
- pgRestoredFromState flag tells main.js to skip its default
selectSample("examples", 0), which otherwise async-clobbers the
restored state ~200ms after page load.
playground-init.js extended: dispatches on hash prefix, stashes payload
to __pendingSampleBundle for pgInit to pick up.
forge-skin.css: .pg-tab / .pg-tab--active / .pg-tab__close /
.pg-tab__rename / .pg-tab__add styling using Forge tokens.
Specs (all green, 15/15 in the full suite):
- tabs.spec.js: 8 cases covering add / switch / rename / delete /
main-protection / dialog flows.
- persistence.spec.js: 2 cases for 3-file reload survival + hash
override.
Phase 4: vendors tutorials/macros/09_for_loop_macro.das + its companion for_loop_macro_mod.das into web/ui/samples/examples/macros/ and registers the pair as "Macros (multi-file)" in samples/data.json. Picking it from the Examples dropdown populates both tabs. This is the wedge case for multi-file: macros must live in a separate module from the one that uses them, so a single-file playground simply cannot demonstrate them. Running the sample emits the `for ((k,v) in tab)` expansion — a real macro module compiled live in the browser. macro-sample.spec.js covers three cases: - two tabs appear after picking the sample (no-WASM) - main.das contains `require for_loop_macro_mod` + macro syntax (no-WASM) - ▶ run produces section-3 output `apple => 10` (@wasm-tagged; skipped by the no-WASM CI gate)
Phase 5: the runCode loop landed back in phase 3 (write every pgState file to MEMFS before callMain). This adds the spec that exercises it: - runs a user-authored two-file program: writes main.das requiring utils with a `hello()` print, asserts the output panel shows "hi, playground". - renaming utils.das breaks the build: after pgRenameFile, `require utils` should fail at compile time, surfaced through stderr. Both tagged @wasm so the no-WASM CI gate skips them.
Phase 6: a "share this playground" button next to ▶ run. Clicking opens a small popover with a shareable URL of the current state, [Copy], and optional [Shorten to is.gd]. URL encoding: every file in pgState + the active filename serialize as JSON, then LZString.compressToEncodedURIComponent → `#z=<base64ish>`. URL-safe out of the box; a 3-file ~40-line state lands well under 1 KB. is.gd shortener: GET https://is.gd/create.php?format=simple&url=… returns the short URL as plain text with permissive CORS. On success the input swaps to the short form; on failure the button briefly says "failed" and the long URL stays put. Vendored: site/files/lz-string.min.js (~5 KB, MIT, lz-string@1.5.0). forge-skin.css: .button_header--ghost variant for the share button; .pg-share popover (header, URL row, copy/shorten/footer). playground-init.js already dispatches `#z=`/`#code=` to pgLoadFiles from phase 3 — this commit just produces those URLs. share.spec.js: four cases. - share popover surfaces the URL with all files (decode round-trip) - opening the URL in a fresh context restores state (one retry, since new-context bootstrap can race the polling pgInit under high parallelism) - is.gd shorten replaces the URL on success (route() stubs the request) - shorten failure surfaces the error and preserves the long URL
Phase 7: the landing hero's "↗ playground" button has been there since the prior PR and emits `playground/index.html#code=<percent-encoded>` — the legacy single-file format. After the multi-file rework that hash shape still has to land cleanly in main.das. hero-handoff.spec.js opens the landing page, plants a marker into the hero's CodeMirror via the CM JS API, clicks the handoff button, captures the popup page, and asserts /playground/ comes up with a single main.das tab carrying the marker. Full suite: 25 specs locally (3 @wasm-tagged), 22 in the CI no-WASM tier.
- Single top toolbar: [tabs] [+] [examples] [share] [run] on the left, [clear] on the right. Drop the per-panel "output" label and the Tests dropdown. Random Sequence moves into Examples; other test samples are removed. - New .main_workspace flex-row contains [code | output] split by a draggable handle (11px hit area, 1px line, amber on drag). Position persists to localStorage; double-click resets to 50/50. - The first splitter pass looked correct but did nothing in real browsers: .main_col carried `flex: 1 1 0 !important`, which !important beats the splitter's inline `style.flex = '0 0 N%'`. Switch to a higher-specificity .main_workspace > .main_col selector with no !important so the inline override actually applies. - Splitter spec drives page.mouse.down/move/up and asserts the *measured* column width — the synthetic-event flavor passed even while the layout never moved. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "source: github.com/borisbat/dasProfile" line under the benchmarks panel was plain text. Wrap it in an <a> with a new `.forge-bench__source` style — amber-dim by default (subtle enough to sit next to fg-faint meta text), full amber + underline on hover. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hook up the legacy dascf-blog Disqus account (shortname "https-borisbat-github-io-dascf-blog") under every blog post. Identifier pins to the post slug so threads stay stable across dev/prod URLs and future renames; old comments stay tied to the original blog URLs in the Disqus admin (can be migrated later via Disqus' URL Mapper). - build_blog.py: render_comments() emits a .forge-post__comments section after the article on post pages only (not index/news/ changelist). - forge.css: section wrapper styled to match Forge (top divider, amber "§ comments" label, dark bg-2 thread container with rule border). `color-scheme: dark` on :root hints Disqus to render its Auto theme as dark. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two surfaces depend on a real keyboard and (for playground) a 5 MB WASM bundle that's mediocre on a phone. On <768px viewports we now: - Landing hero: hide the live CodeMirror panel + sample tabs + run / playground buttons. A static <pre><code class="language-daslang"> block, tokenized by the existing highlight.js, takes its place. The terminal frame (dots + filename) is kept so the visual identity stays intact. forge.js short-circuits the CM init on mobile so the editor doesn't materialize behind the static block. - /playground/: a synchronous mobile detector tags <html> with `.is-pg-mobile` so CSS hides the IDE chrome, and on DOMContentLoaded overrides `pageInit` so the body.onload bootstrap renders a Forge- styled "open on a laptop" notice instead of fetching daslang_static.js / .wasm. Spec asserts zero requests for either resource on mobile. The (already-hidden-on-mobile) playground nav link in .forge-nav__links stays as-is; the redundant "playground" version label on the playground page is also hidden when the notice is showing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Punch list from a full-site audit: Block fixes - Mobile nav: ≡ hamburger button in the right-side chip strip on every Forge nav (landing, downloads, blog template, playground). Toggles `.forge-nav.is-open`, which reveals `.forge-nav__links` as an absolutely-positioned dropdown panel under the nav. Phone users can now reach docs / benchmarks / downloads / blog / community. - Mobile blog/changelist grid: `.forge-blog-item` carried only its desktop `140px 90px 1fr` shape, which squeezed titles into ~94px at 390px viewport (one word per line). Added a mobile override stacking date+tag on row 1, body on row 2. - Missing image in blog/instruments: `/images/call_tree.PNG` is a Hexo-era absolute path with no asset on this site. Rewrote the sentence to drop the inline image. Cleanup - Removed dead `runTests`/`runTest`/`outputPool` machinery from web/ui/src/main.js (and the mirrored site/playground/main.js) — the Tests dropdown that drove them is gone. Simplified the dropdown populate loop now that only Examples remains. - Deleted orphan CodeMirror files from web/ui/src/: codemirror.css, codemirror.min.js, eclipse.css. The playground now loads the Forge-themed CodeMirror bundle from /files/cm/ — these were ~232 KB of dead bytes copied per CI deploy. site/.gitignore entries for the same files dropped. - Hide `.forge-nav__version` at <480 px so the version label stops crowding [github ↗] on narrow phones. - Fixed "0.6.0-RC1 0.6.0-RC2" double label in the 2026-02-28 news entry (left over from a partial RC1→RC2 edit). - Sphinx `version` was '0.6' while the site shows v0.6.2; bumped to '0.6.2' to match. - hero-handoff.spec.js: added `retries: 1` for the two-window race that flakes under high parallelism (passes alone, fails ~1/3 in full suite). Suite is green-on-retry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uite The audit flagged the README as stale — all three recently-landed surfaces (mobile fallbacks, multi-file playground, Disqus comments) had no mention. Updated: - Replaced the desktop-only summary with a "Surface map" table covering hero / playground / blog / nav behavior on desktop vs <768 px. - Expanded the playground section: multi-file tab CRUD, ▶ run via MEMFS write loop, ↗ share (#z= compressed hash + is.gd shorten), 250 ms autosave to localStorage, splitter persistence. - Added a Playwright suite section: 28 specs, no-WASM in ~5 s, pointer to the CI workflow. - Refreshed the file tree under site/ — new playground-*.js files, the site/tests/playground/ suite, the relocated CodeMirror bundle at site/files/cm/, and lz-string.min.js. - Updated the "/playground/ blank page" gotcha now that CodeMirror lives in /files/cm/ rather than web/ui/src/. - Added a mobile-gate gotcha (hard-refresh required after toggling DevTools' device toolbar). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Round 1 from Copilot. All four findings were real. - pages.yml + playground-e2e.yml: stage every `playground-*.js` (init, tabs, share, splitter) not just init.js. CI's 404 storm on playground-tabs/share/splitter is the root cause of the failed playground-e2e run; same bug in pages.yml would have landed the regression in prod. Globbing also picks up future additions. - playground-tabs.js: drop `/` from the rename regex. The previous pattern accepted `dir/foo.das`, but Emscripten MEMFS's `FS.writeFile` doesn't auto-create parents — Run would silently ENOENT. Flat namespace is enough for the playground. - forge.js escapeHtml: also escape `"` and `'`. The news renderer interpolates `n.link` inside `href="..."`; a `"` in the URL would break out and inject markup. `_news/*.md` is committed so practical risk is near-zero, but the fix is one line and defensive. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two more from Copilot. Both real. - Atom feed default `--site-url` was `https://dascript.org`; neither workflow passed `--site-url`, so `blog/feed.xml` would have advertised old-domain URLs once deployed at daslang.io. Subscribers' clients follow `<link>`/`<id>` URLs to fetch posts — those would have hit a domain that no longer serves the site. Bumped the default to daslang.io AND pinned `--site-url https://daslang.io` from both workflows so the default can't silently drift again. Updated the stale README header that still pointed at dascript.org. - runCode() wrote every current pgState file to MEMFS but never unlinked files the user had deleted/renamed. Concrete bug: open utils.das, write `require utils` in main, run (writes utils.das to MEMFS), then delete utils.das tab, run again — still works from stale MEMFS state, executed program no longer matches visible tabs. Fixed by tracking a module-level Set of last-written names; each run unlinks stale-from-prior-run names (ENOENT tolerated) before writing current. Mirrored to site/playground/main.js. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three real, one false alarm.
Real:
- doc/source/_static/custom.css: `@import` for Inter Tight + JetBrains
Mono lived AFTER a `:root` rule, so browsers silently drop it (CSS 2.1
§6.3). Docs were quietly falling back to system fonts. Moved the
import to the very top.
- playground-init.js + hero-handoff.spec.js: the hash-payload dispatch
stashed `__pendingSampleBundle` but never set `pgRestoredFromState`.
main.js's default `selectSample("examples", 0)` then ran and the
data.json fetch sometimes beat pgLoadFiles, overwriting the marker
text with hello.das. Visible as a flake in CI (slower http.server)
but a real correctness bug on cold-cache loads in prod too. Set the
flag; also harden the spec to wait for the marker content, not just
for pgState's structure to materialize. Three consecutive local runs
green.
- web/ui/index.html: legacy standalone IDE entry point referencing
files we deleted (codemirror.{min.js,css}, eclipse.css). Nothing in
CI/build references it. Removed.
False alarm:
- site/files/cm/daslang-keywords.js: Copilot flagged enum / typedef /
with / aka / reinterpret / upcast / range64 / urange / urange64 as
absent. They're all present (verified against
src/parser/ds2_lexer.lpp's `DAS_*` returns). Reply on the thread —
no code change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…stage
playground-tabs.js: tryInit() polled every 30ms for `window.code` indefinitely.
On mobile, pageInit is short-circuited so the variable never appears, leaving
a 33Hz setTimeout chain running for the life of every mobile tab. Skip the
init when the .is-pg-mobile gate class is present.
pages.yml: the WASM build step keeps continue-on-error so docs/blog still
ship through emsdk hiccups, but the staging block then accepted a half-built
web/output and deployed a playground whose Run button 404'd the runtime.
Tighten the gate to require both daslang_static.{js,wasm} on disk before
staging any playground files — Pages preserves the prior deploy, so the
visible /playground/ keeps working instead of going silently broken.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-miss placeholder
playground-init: the `#z=` hash decode stashed `__pendingSampleBundle =
payload.files` but dropped `payload.active`. When playground-tabs.js's
tryInit consumed the bundle before this script's tryApply loop resolved,
`pgLoadFiles(bundle)` ran without an active argument and fell back to
main.das, so a shared URL whose author was viewing utils.das landed on the
recipient's screen with main.das in front. Stash `__pendingSampleActive`
alongside the bundle and consume it in both tab-init paths.
pages.yml: round-4 staging gate (require daslang_static.{js,wasm} before
copying playground files) was based on an incorrect read of
actions/deploy-pages — Pages publishes _site as a complete snapshot, not a
layer over the prior deploy, so a missing _site/playground/ 404s the route
instead of preserving the previous runtime. Stage site/playground/
placeholder.html into _site/playground/index.html on the missing-artifact
branch so /playground/ keeps resolving to something useful while the next
merge re-rolls the runtime.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
License footer in site/index.html, site/downloads.html, and site/blog/template.html displayed "MIT" but the repository LICENSE is BSD 3-Clause (Gaijin Entertainment, 2019-2023). The blog template fixes every generated blog page on the next build, so no per-page edits needed. playground-tabs.js: pgSwitchFile updated pgState.active and swapped the visible Doc but never called autosave(). User-initiated tab clicks therefore didn't persist; a page reload restored the previous active tab (or main.das if no content edit had triggered autosave on `change` yet). The other state-changing entry points (pgAddFile/Delete/Rename/LoadFiles) were already saving correctly — only the bare tab-click path was leaking. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…redesign site: Forge redesign — landing, blog, playground, mobile
Documentation: introduce an "External modules" section in the docs TOC (after stdlib) with a single dasImgui entry. Cross-link points at borisbat.github.io/dasImgui/ where the matching theme + intersphinx-based backlink land in a sibling PR. News: 0.6.2 release announcement for the landing-page § 05 strip — the release was promoted on 2026-05-13. mouse-data: four cards from this session's wrap-up curation. GitHub Pages deploy-snapshot semantics, CSS @import ordering rule, !important-vs-inline debugging anchor, CodeMirror 5 multi-Doc autosave discipline. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
web/step0_emsdk_install.sh is a one-liner that just clones the emsdk
wrapper repo. The toolchain itself — emsdk/upstream/emscripten/, which
the cmake -DCMAKE_TOOLCHAIN_FILE arg points at — lands via the
install+activate pair that step1_emsdk_activate_linux.sh does locally.
pages.yml was calling step0 but never the install/activate steps, so the
WASM build always failed at cmake-configure ("Could not find toolchain
file"). continue-on-error: true on this step paints it green in the run
summary, and the post-build staging silently skipped the missing-artifact
copies — until the round-5 placeholder gate started serving the
"Runtime rebuild in progress" page instead, which is what surfaced this.
Add the two missing commands. The build should now actually produce
web/output/daslang_static.{js,wasm}; the existing staging gate will then
take the real path instead of the placeholder.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…al-modules-dasimgui docs: External modules section + dasImgui crosslink
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
See Commits and Changes for more details.
Created by
pull[bot] (v2.0.0-alpha.4)
Can you help keep this open source service alive? 💖 Please sponsor : )