Repository navigation
Conversation
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
left a comment
There was a problem hiding this comment.
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
-
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, includingGET /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 aworker_threadsWorker andterminate()it after a deadline (for example 1–2 s), returning{ ok: false, error: 'timeout' }. Also cap the length oftextandpattern. -
Zero-length matches with the
uflag loop up to the guard (regex-engine.mjs:46).lastIndex += 1lands 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 whenregex.unicode || regex.unicodeSets, the wayString.prototype.matchAlldoes. For example:regex.lastIndex = advance(text, regex.lastIndex, regex.unicode || regex.unicodeSets), whereadvancesteps 2 over a surrogate pair. Please add a test for it.
Suggestions
- Request body size (
server.mjs:79):readJsonBodybuffers 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
HostorOriginheader says. Requiring a loopbackHost, and a matchingOriginon POST, guards against DNS-rebinding and cross-site requests.plugins/1602winxp/pomodoro-sit-timer/miniapp/node/server.mjs(theLOOPBACK_NAMES/isLoopbackAuthorityhelpers) is a good example to follow. - Guard hit is silent (
regex-engine.mjs:47): whenMAX_MATCHESis reached, return atruncated: trueflag 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 becomenull. Usenullfor both so the UI can show "(未匹配)" consistently. - Error status: an invalid JSON body returns 400 with the raw
SyntaxErrormessage, 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'sREADME.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) { |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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 ?? ''])) |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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')) { |
There was a problem hiding this comment.
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.
| | [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) | |
There was a problem hiding this comment.
Suggestion: the other rows link to the plugin's README.zh-CN.md. Please use plugins/lym308758/regex-lab/README.zh-CN.md.
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
index–end), the matchedsubstring, numbered capture groups, and named capture groups.
$1,$<name>,$$,$&,$`,$'tokens.g/i/m/s/u/y.Architecture
miniapp/node/regex-engine.mjs— pure regex functions (match / replace), no DOM, shared bythe Node HTTP API and the unit tests.
miniapp/node/server.mjs— Node entry (start(context) -> { dispose }) serving the page and asmall JSON API (
/api/match,/api/replace), built onnode:httponly.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).client, so the page was not opened in the side panel. The Node backend is covered by
tests/server.test.mjsagainst a realnode:httpserver.Note
Built with the assistance of an AI coding agent (WorkBuddy).
Need help on this PR? Tag
@codesmith-botwith what you need.