Skip to content

Add Regex Lab Mini App (regex-lab) - #57

Open
Lym308758 wants to merge 1 commit into
MiniMax-AI:mainfrom
Lym308758:feat/regex-lab
Open

Lym308758 wants to merge 1 commit into
MiniMax-AI:mainfrom
Lym308758:feat/regex-lab

Conversation

@Lym308758

@Lym308758 Lym308758 commented Oct 22, 2026 •

Copy link
Copy Markdown

Regex Lab · 正则实验室

A live regular-expression workbench that runs inside MiniMax Code. Type a pattern and
some sample text, watch every match appear with its capture groups and named groups,
and preview a replacement — all computed by the Mini App's own Node backend. Nothing
is sent anywhere; the text never leaves the app.

What it does

  • Live matching against sample text, with match position (index–end), the matched
    substring, numbered capture groups, and named capture groups.
  • Replacement preview using the standard $1, $<name>, $$, $&, $`, $' tokens.
  • Flag toggles for g / i / m / s / u / y.
  • Invalid-pattern feedback — a bad regex shows the engine's error instead of failing silently.

Architecture

  • miniapp/node/regex-engine.mjs — pure regex functions (match / replace), no DOM, shared by
    the Node HTTP API and the unit tests.
  • miniapp/node/server.mjs — Node entry (start(context) -> { dispose }) serving the page and a
    small JSON API (/api/match, /api/replace), built on node:http only.
  • miniapp/client/index.html — dependency-free page (light/dark) that calls the API.

Validation

  • node scripts/validate.mjs plugins/lym308758/regex-lab → OK (0 errors, 0 warnings);
    full-repo node scripts/validate.mjs → repository: OK.
  • node --test plugins/lym308758/regex-lab/tests/ → 16/16 pass (engine + HTTP handler + lifecycle).
  • Not verified inside the MiniMax Code host UI: this environment has no MiniMax Code desktop
    client, so the page was not opened in the side panel. The Node backend is covered by
    tests/server.test.mjs against a real node:http server.

Note

Built with the assistance of an AI coding agent (WorkBuddy).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

A live regular-expression workbench for MiniMax Code: test patterns against
sample text, inspect capture groups and named groups, and preview replacements.
Computed entirely in the Mini App's Node backend; nothing leaves the machine.

Built with the assistance of an AI coding agent (WorkBuddy).

@MyPrototypeWhat MyPrototypeWhat left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for Regex Lab. It's a tidy, dependency-free plugin, and the scope is right: only plugins/lym308758/regex-lab/ plus one row in each root README. MIT license is included, the manifests match the other plugins, node --test passes (16/16), and npm run validate reports plugins/lym308758/regex-lab: OK. Invalid patterns come back as { ok: false, error } instead of crashing, $<name> / $1 / $$ replacement uses native String.prototype.replace and works ((?<x>a) → [$<x>] gives [a]b), and dispose closes the server and removes the abort listener.

I found two things that need fixing before merge.

Blocking

  1. A catastrophic-backtracking pattern freezes the whole backend (miniapp/node/regex-engine.mjs:22, :42, :68). The user's pattern runs synchronously on the server's event loop with no time limit. runMatch('a'.repeat(32) + '!', '^(a+)+$') never returns: I killed it after 5 seconds. Once that happens, every other request, including GET /dashboard, stalls until the process is killed. Because the UI re-runs on every keystroke, it is easy to trigger by accident while typing a pattern. Suggested fix: run match/replace in a worker_threads Worker and terminate() it after a deadline (for example 1–2 s), returning { ok: false, error: 'timeout' }. Also cap the length of text and pattern.

  2. Zero-length matches with the u flag loop up to the guard (regex-engine.mjs:46). lastIndex += 1 lands in the middle of a surrogate pair. In unicode mode the engine moves it back to the start of the code point, so the same empty match repeats. runMatch('😀', '', 'gu') returns 100001 identical matches at index 0 (about 195 KB of JSON) instead of 2. Advance by code point when regex.unicode || regex.unicodeSets, the way String.prototype.matchAll does. For example: regex.lastIndex = advance(text, regex.lastIndex, regex.unicode || regex.unicodeSets), where advance steps 2 over a surrogate pair. Please add a test for it.

Suggestions

  • Request body size (server.mjs:79): readJsonBody buffers an unlimited amount. Reject with 413 above a cap (for example 1 MB) and stop reading.
  • Host/Origin checks: the server accepts requests whatever their Host or Origin header says. Requiring a loopback Host, and a matching Origin on POST, guards against DNS-rebinding and cross-site requests. plugins/1602winxp/pomodoro-sit-timer/miniapp/node/server.mjs (the LOOPBACK_NAMES / isLoopbackAuthority helpers) is a good example to follow.
  • Guard hit is silent (regex-engine.mjs:47): when MAX_MATCHES is reached, return a truncated: true flag so the UI can say so. A lower cap (for example 10k) would also keep responses small.
  • Named groups (regex-engine.mjs:85): an unmatched named group becomes '', but unmatched numbered groups become null. Use null for both so the UI can show "(未匹配)" consistently.
  • Error status: an invalid JSON body returns 400 with the raw SyntaxError message, which is fine. Consider a stable { error: 'invalid_json' }.
  • README.zh-CN.md row links to plugins/lym308758/regex-lab/. The other rows in the Chinese README link to the plugin's README.zh-CN.md.
  • Wording: the README row and the description say everything is "computed entirely in the Mini App", but matching actually runs in the plugin's local Node backend. It's still all on the user's machine, so "computed locally" would be more accurate.

if (regex.global) {
let execResult;
let guard = 0;
while ((execResult = regex.exec(text)) !== null) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: the user's pattern runs synchronously on the server event loop with no time limit. ^(a+)+$ against 'a'.repeat(32)+'!' never returns, and every other request (including /dashboard) hangs with it. Please run this in a worker_threads Worker with a deadline and terminate(), and cap the text and pattern length.

matches.push(describeMatch(execResult));
// A zero-width match leaves lastIndex unchanged and would loop forever;
// nudge it forward so the next search starts after the current position.
if (execResult.index === regex.lastIndex) regex.lastIndex += 1;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Blocking: with the u/v flag, += 1 lands inside a surrogate pair and the engine moves it back, so runMatch('😀', '', 'gu') returns 100001 identical matches at index 0. Advance by code point when regex.unicode || regex.unicodeSets.

// A zero-width match leaves lastIndex unchanged and would loop forever;
// nudge it forward so the next search starts after the current position.
if (execResult.index === regex.lastIndex) regex.lastIndex += 1;
if (++guard > MAX_MATCHES) break;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: tell the caller when this guard trips (for example truncated: true) and consider a lower cap. 100k matches is a very large response.

}
const namedGroups =
m.groups != null
? Object.fromEntries(Object.entries(m.groups).map(([key, value]) => [key, value ?? '']))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: unmatched named groups become '' here, but unmatched numbered groups become null. Using null for both would be consistent.

function readJsonBody(request) {
return new Promise((resolve, reject) => {
const chunks = [];
request.on('data', (chunk) => chunks.push(chunk));

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: there is no body size limit. Reject with 413 once the total size passes a cap (for example 1 MB) and stop reading.

sendHtml(response, 200, html);
return;
}
if (request.method === 'POST' && (route === '/api/match' || route === '/api/replace')) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: check that Host is loopback, and that Origin matches on POST, before routing. See plugins/1602winxp/pomodoro-sit-timer/miniapp/node/server.mjs (isLoopbackAuthority) for an example.

Comment thread README.zh-CN.md
| [Anatomy 画廊](plugins/zch0227/anatomy-gallery/README.zh-CN.md) | 集中展示 anatomy 技能做出的交互式等距图解:按自建展区挂墙,全屏查看明暗两版,保留全部历史版本;在画廊里让 Agent 新建或修改作品,每次运行都有过程记录。自带 anatomy 技能。需要 MiniMax Code 3.1.2 或更高版本 | [zch0227](https://github.com/zch0227) |
| [仓库透视](plugins/yujiachen-y/repo-xray/README.zh-CN.md) | 从 Git 历史看懂一个代码仓库:可逐层下钻的代码地图、热点与 Bug 磁铁、高风险文件、负责人与知识流失、提交节奏与救火记录、技术栈;支持 GitHub、GitLab 和本机仓库,全部在本机分析 | [yujiachen-y](https://github.com/yujiachen-y) |
| [磁盘透视](plugins/zch0227/disk-insight/README.zh-CN.md) | 查看 macOS 和 Windows 的磁盘空间去向:旭日图空间地图,按三档风险分类的垃圾文件,大文件和重复文件,代码仓库和 Git worktree 的占用;任何条目都可以问 Agent,选中的条目在确认框里逐一列出,确认后直接永久删除。需要 MiniMax Code 3.1.2 或更高版本 | [zch0227](https://github.com/zch0227) |
| [Regex Lab](plugins/lym308758/regex-lab/) | 实时正则工作台:用样例文本测试正则,逐条查看匹配与捕获组 / 命名组,并预览替换结果;计算全部在 Mini App 内完成,数据不出本机 | [lym308758](https://github.com/lym308758) |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggestion: the other rows link to the plugin's README.zh-CN.md. Please use plugins/lym308758/regex-lab/README.zh-CN.md.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants