feat: hand written parser - #40717
Conversation
|
Looks like this PR is not ready to merge, because of the following issues:
Please fix the issues and try again If you have any trouble, please check the PR guidelines |
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThe PEG grammar entry point is replaced by a scanner-based parser. Character and emoticon helpers support ordered inline matching, while block parsing adds Markdown-like constructs including links, formatting, lists, spoilers, KaTeX, task items, and tables. PEG loading and build configuration are removed. ChangesMessage parser implementation
Estimated code review effort: 5 (Critical) | ~90+ minutes Sequence Diagram(s)sequenceDiagram
participant Caller
participant parse
participant Scanner
participant Root
Caller->>parse: provide message input and options
parse->>Scanner: scan characters and delimiters
Scanner-->>parse: positions and matched slices
parse->>Root: append inline and block nodes
Root-->>Caller: return parsed document
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches 💡 1🛠️ Fix failing CI checks 💡
Warning Review ran into problems🔥 ProblemsErrors were encountered while retrieving linked issues. Errors (1)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey @amitkumarashutosh , good work getting this done. The class is clean and readable overall. I do have a few thoughts on the OOP design. The core issue is that methods like advance(), save(), restore(), and pos being public are exposing the internal mechanics of how the scanner works, rather than what the scanner does from the caller's perspective. This breaks encapsulation — callers shouldn't need to know or care how the scanner moves through the input internally. Let me use an example. Imagine you order a car from Factory A. Its public interface is start(), drive(), brake(), and getSpeed(). Now Factory B delivers a car with those same methods plus increaseSpeed(), decreaseSpeed(), and setRPM(). Most people would prefer Factory A's car — not because Factory B's car is broken, but because Factory A made a decision about what the driver should and shouldn't need to think about. The engine is managing speed under the hood, the driver just presses the pedal. Factory B is forcing the driver to manage internals that should be invisible, which makes the experience more complex and error-prone. Expose too much, and the driver causes a crash not because they were reckless, but because the interface invited the mistake. The same applies here — the Scanner class is our car, and the caller is our driver. advance(n) is a raw cursor movement that exposes internal position management directly to the caller. Instead, the scanner should move itself forward as a side effect of higher-level operations. Methods like consume(), consumeWhile(predicate), or expect(literal) would advance internally and return meaningful results — the caller should be asking what to consume, not how many characters to move. save() and restore() expose the backtracking mechanism as a manual two-step process. This is fragile because a caller can save and forget to restore. A pattern like tryConsume(literal) or a scoped attempt(() => ...) callback would hide the save/restore cycle entirely and make misuse much harder. pos being public lets anyone outside the class read and reason about the raw cursor index. Position is an implementation detail. If the caller needs location info for something like error reporting, a dedicated method like getLocation() returning something meaningful would serve that need without leaking the internals. sliceFrom(savedPos) requires the caller to hold a raw position integer and pass it back in, which ties the caller directly to internal state. A mark() method returning an opaque token, paired with getSliceSinceMarked(token), would achieve the same result without exposing the position type at all.
A well-designed class should make the right usage easy and the wrong usage hard. Right now the public API of Scanner requires callers to know a lot about how the internals work in order to use it correctly. The goal is to push that knowledge inward — hide the cursor, hide the backtracking, and expose only the operations that make sense at the scanning level.
| if (scanner.matches('mailto:')) { | ||
| scanner.advance(7); | ||
| } |
There was a problem hiding this comment.
Looks like a magic number slipped through here! Could you please fix this one and double-check the rest of the code for any others?
37e61da to
c673fba
Compare
|
Hey @amitkumarashutosh , I'm getting a build failure in |
c673fba to
65fd782
Compare
@ahmed-n-abdeltwab, I resolved the unused variable ( |
Thanks. It's always a good idea to double-check everything before pushing a commit. It's okay to make these kinds of mistakes; I do it all the time and they're hard to catch. That's why I usually run the build to ensure everything compiles properly, and then run the tests and lint checks to make sure I don't mess up the code or the project. It's like evolving a third eye that's looking out for those mistakes! :) |
…or rules (710/717)
|
Nice work 🥳, @amitkumarashutosh! The cubic review is now underway. Please review and address its feedback once it finishes |
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
packages/message-parser/src/chars.ts (1)
44-47: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove implementation comments from this TypeScript file.
The inline annotations, section header, and ordering note conflict with the repository rule to avoid implementation comments. Move important rationale to documentation or tests instead.
As per coding guidelines,
**/*.{ts,tsx,js}files should avoid code comments in the implementation.Also applies to: 55-55, 84-84
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/message-parser/src/chars.ts` around lines 44 - 47, Remove the inline implementation comments from the character-range conditions in chars.ts, including the annotations at the referenced locations and the section header or ordering note. Preserve the existing conditions and ordering; move only rationale that is necessary for maintainers into appropriate documentation or tests.Source: Coding guidelines
packages/message-parser/src/parser.ts (1)
85-98: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider trimming the section-banner and step-by-step comments.
Banners (lines 85, 98, 493, 1267, 1676) and running narration (
// consume '@',// consume ')') restate the code. The genuinely useful ones are the grammar-rationale comments (e.g. lines 552, 577, 1005, 1467) — worth keeping those and dropping the rest.As per coding guidelines, "Avoid code comments in the implementation".
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/message-parser/src/parser.ts` around lines 85 - 98, Remove non-actionable section banners and step-by-step narration comments in parser.ts, including banners near the constants/helpers and other cited sections and comments such as “consume '@'” or “consume ')'”. Preserve grammar-rationale comments that explain non-obvious parsing decisions, including those around the cited grammar logic.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/message-parser/src/parser.ts`:
- Around line 1751-1755: Initialize align in the table-column alignment logic
before the conditional chain with the PEG grammar’s default for a colon-less
column, then retain the existing left, right, and center overrides. Ensure
aligns.push receives a valid TableCellAlignment for plain `---` columns.
- Around line 681-684: Update the trailing-domain cleanup loop in the parser to
remove both `.` and `-` characters, matching the documented behavior for domains
such as `joe.com-`; preserve the existing domain-start boundary.
- Around line 167-170: Make the parser re-entrant by removing the module-level
mutable guards skipBold, skipItalic, skipStrike, and skipReferences and storing
them in per-parse state passed through the relevant parsing functions. Update
each guard site and nested parseInline/scanner call to read and mutate that
state, ensuring guard values are restored with try/finally when temporarily
changed so exceptions cannot leak state across parse() calls.
- Around line 1501-1520: Update tryOrderedList to track the current item’s start
separately from the list start, and when a continuation item is malformed (such
as missing the required dot or space), backtrack only to that item start and
break rather than returning null. Preserve already-parsed ordered items while
leaving the malformed line for subsequent parsing, matching the unordered-list
behavior.
---
Nitpick comments:
In `@packages/message-parser/src/chars.ts`:
- Around line 44-47: Remove the inline implementation comments from the
character-range conditions in chars.ts, including the annotations at the
referenced locations and the section header or ordering note. Preserve the
existing conditions and ordering; move only rationale that is necessary for
maintainers into appropriate documentation or tests.
In `@packages/message-parser/src/parser.ts`:
- Around line 85-98: Remove non-actionable section banners and step-by-step
narration comments in parser.ts, including banners near the constants/helpers
and other cited sections and comments such as “consume '@'” or “consume ')'”.
Preserve grammar-rationale comments that explain non-obvious parsing decisions,
including those around the cited grammar logic.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 57d36c1e-1e66-4f23-9d84-f8cbaeeedb01
📒 Files selected for processing (4)
packages/message-parser/src/chars.tspackages/message-parser/src/index.tspackages/message-parser/src/parser.tspackages/message-parser/src/scanner.ts
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
- GitHub Check: cubic · AI code reviewer
⚠️ CI failures not shown inline (1)
GitHub Check: Dionisio QA: Some checks did not pass
Conclusion: failure
**Conclusion:** failure
### Steps
- ✅ **No merge conflicts**
- ❌ **QA assured** — This PR is missing the 'stat: QA assured' label
- ✅ **Mergeable**
- ❌ **Has milestone or project** — This PR is missing the required milestone or project
- ✅ **Valid PR title**
- ✅ **Correct target version**
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js}
📄 CodeRabbit inference engine (.cursor/rules/playwright.mdc)
**/*.{ts,tsx,js}: Write concise, technical TypeScript/JavaScript with accurate typing in Playwright tests
Avoid code comments in the implementation
Files:
packages/message-parser/src/scanner.tspackages/message-parser/src/index.tspackages/message-parser/src/chars.tspackages/message-parser/src/parser.ts
🧠 Learnings (3)
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In the Rocket.Chat repository, do not reference Biome lint rules in code review feedback. Biome is not used even if biome.json exists; only reference Biome rules if there is explicit, project-wide usage documented. For TypeScript files, review lint implications without Biome guidance unless the project enables Biome rules.
Applied to files:
packages/message-parser/src/scanner.tspackages/message-parser/src/index.tspackages/message-parser/src/chars.tspackages/message-parser/src/parser.ts
📚 Learning: 2026-02-26T19:25:44.063Z
Learnt from: gabriellsh
Repo: RocketChat/Rocket.Chat PR: 38778
File: packages/ui-voip/src/providers/useMediaSession.ts:192-192
Timestamp: 2026-02-26T19:25:44.063Z
Learning: In this repository (RocketChat/Rocket.Chat), Biome lint rules are not used even if a biome.json exists. When reviewing TypeScript files (e.g., packages/ui-voip/src/providers/useMediaSession.ts), ensure lint suggestions do not reference Biome-specific rules. Rely on general ESLint/TypeScript lint rules and project conventions instead.
Applied to files:
packages/message-parser/src/scanner.tspackages/message-parser/src/index.tspackages/message-parser/src/chars.tspackages/message-parser/src/parser.ts
📚 Learning: 2026-05-06T12:21:44.083Z
Learnt from: juliajforesti
Repo: RocketChat/Rocket.Chat PR: 40256
File: apps/meteor/client/components/CreateDiscussion/CreateDiscussion.tsx:121-149
Timestamp: 2026-05-06T12:21:44.083Z
Learning: Field wrappers in rocket.chat/fuselage-forms (Field, FieldLabel, FieldRow, FieldError, FieldHint) auto-create htmlFor/id associations, aria-describedby, and role="alert" for errors. Do not manually set htmlFor, id, aria-describedby, or role attributes when using these wrappers. This automatic wiring does not apply to plain rocket.chat/fuselage components, which require explicit ID wiring per the accessibility docs. In code reviews, prefer using fuselage-forms wrappers for form fields and verify there is no unnecessary manual ID/aria wiring in files that use these wrappers. If a component uses plain fuselage components, ensure proper id wiring as per docs.
Applied to files:
packages/message-parser/src/scanner.tspackages/message-parser/src/index.tspackages/message-parser/src/chars.tspackages/message-parser/src/parser.ts
🪛 ast-grep (0.45.0)
packages/message-parser/src/parser.ts
[warning] 90-92: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})\\.(\\d{3})${OPTIONAL_TIMEZONE_OFFSET}$,
)
Note: [CWE-1333] Inefficient Regular Expression Complexity
(regexp-from-variable)
[warning] 93-93: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^(\\d{4})-(\\d{2})-(\\d{2})T(\\d{2}):(\\d{2}):(\\d{2})${OPTIONAL_TIMEZONE_OFFSET}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity
(regexp-from-variable)
[warning] 94-94: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^(\\d{2}):(\\d{2}):(\\d{2})${OPTIONAL_TIMEZONE_OFFSET}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity
(regexp-from-variable)
[warning] 95-95: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(^(\\d{2}):(\\d{2})${OPTIONAL_TIMEZONE_OFFSET}$)
Note: [CWE-1333] Inefficient Regular Expression Complexity
(regexp-from-variable)
🪛 OpenGrep (1.26.0)
packages/message-parser/src/parser.ts
[ERROR] 764-764: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
[ERROR] 775-775: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
[ERROR] 785-785: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
[ERROR] 787-787: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
[ERROR] 1179-1179: Dynamic command passed to child_process.exec/execSync. Use child_process.execFile or spawn with an argument array instead.
(coderabbit.command-injection.exec-js)
🔇 Additional comments (5)
packages/message-parser/src/chars.ts (1)
1-23: LGTM!Also applies to: 25-43, 48-53, 56-82
packages/message-parser/src/scanner.ts (1)
1-45: LGTM!packages/message-parser/src/parser.ts (2)
1173-1177: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueName the
32-char lookahead window.A magic number slipped in here, matching the earlier magic-number feedback on this PR. Extract it as a module constant (e.g.
MAX_EMOJI_SEQUENCE_LENGTH) alongside the other constants at lines 87-96.
88-88: 🩺 Stability & AvailabilityAlign the package baseline with the new regex support.
packages/message-parser/tsconfig.jsontargetses2020, butUNICODE_EMOJIuses the ES2024 RegExpvflag. Since this package is exported fromdist/messageParser.js, add browser/Node baseline documentation for Chrome 112+, Firefox 116+, Safari 17+, and Node 20+, or guard construction with an ASCII-safe fallback.packages/message-parser/src/index.ts (1)
2-2: LGTM!Also applies to: 20-20
| let skipBold = false; | ||
| let skipItalic = false; | ||
| let skipStrike = false; | ||
| let skipReferences = false; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Module-level mutable guards make the parser non-reentrant and leak state on throw.
skipBold/skipItalic/skipStrike/skipReferences are process-wide singletons reset by plain assignment at lines 518-520, 567-569, 608-610, and 960-962. If anything between set and reset throws (nested parseInline, a malformed table cell scanner at line 1709), the flag stays true and every subsequent parse() call in that process silently loses bold/italic/strike/link parsing. Threading the flags through a parser state object is the durable fix; try/finally at each site is the minimum.
♻️ Minimum containment at each guard site
- skipBold = true;
- const content = parseInline(scanner, options, delimiter);
- skipBold = false;
+ skipBold = true;
+ let content: Inlines[];
+ try {
+ content = parseInline(scanner, options, delimiter);
+ } finally {
+ skipBold = false;
+ }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/message-parser/src/parser.ts` around lines 167 - 170, Make the
parser re-entrant by removing the module-level mutable guards skipBold,
skipItalic, skipStrike, and skipReferences and storing them in per-parse state
passed through the relevant parsing functions. Update each guard site and nested
parseInline/scanner call to read and mutate that state, ensuring guard values
are restored with try/finally when temporarily changed so exceptions cannot leak
state across parse() calls.
| let align: TableCellAlignment; | ||
| if (left && right) align = 'center'; | ||
| else if (right) align = 'right'; | ||
| else if (left) align = 'left'; | ||
| aligns.push(align); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
align is unassigned for plain --- columns.
The chain has no else, so a column with no colons (| --- |, the most common case) leaves align uninitialized: TS2454 under strict, or undefined pushed into the alignment array otherwise. Give it an explicit default.
🐛 Default the alignment
- let align: TableCellAlignment;
- if (left && right) align = 'center';
- else if (right) align = 'right';
- else if (left) align = 'left';
- aligns.push(align);
+ let align: TableCellAlignment;
+ if (left && right) align = 'center';
+ else if (right) align = 'right';
+ else align = 'left';
+ aligns.push(align);Confirm the intended default matches the PEG grammar's output for a colon-less column before merging.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let align: TableCellAlignment; | |
| if (left && right) align = 'center'; | |
| else if (right) align = 'right'; | |
| else if (left) align = 'left'; | |
| aligns.push(align); | |
| let align: TableCellAlignment; | |
| if (left && right) align = 'center'; | |
| else if (right) align = 'right'; | |
| else align = 'left'; | |
| aligns.push(align); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/message-parser/src/parser.ts` around lines 1751 - 1755, Initialize
align in the table-column alignment logic before the conditional chain with the
PEG grammar’s default for a colon-less column, then retain the existing left,
right, and center overrides. Ensure aligns.push receives a valid
TableCellAlignment for plain `---` columns.
There was a problem hiding this comment.
All reported issues were addressed across 4 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## develop #40717 +/- ##
===========================================
- Coverage 68.69% 68.23% -0.46%
===========================================
Files 4139 4153 +14
Lines 159112 151397 -7715
Branches 27917 25951 -1966
===========================================
- Hits 109300 103304 -5996
+ Misses 44666 42939 -1727
- Partials 5146 5154 +8
Flags with carried forward coverage won't be shown. Click here to find out more. 🚀 New features to boost your workflow:
|
ahmed-n-abdeltwab
left a comment
There was a problem hiding this comment.
Please remove the old parser and run the tests
Just the grammar file, or the peggy loader folder as well? |
Anything we aren't using anymore should be removed, your changes should replace it and work |
There was a problem hiding this comment.
All reported issues were addressed across 4 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
c8822be to
122d5dc
Compare
122d5dc to
62d2249
Compare
ahmed-n-abdeltwab
left a comment
There was a problem hiding this comment.
Hey Amit, try removing anything related to PegJS as well. I tried it myself and the project runs perfectly fine.
Please remove these directories:
packages/peggy-loaderpackages/message-parser/loaders
Also update these files remove Peggy from them:
packages/message-parser/jest.config.tspackages/message-parser/package.jsonpackages/message-parser/webpack.config.ts
After making the changes, try running, building, and testing the project. Everything should work normally, because we no longer need these
|
Done. The build, tests, and all checks are passing. |
|
Hey @amitkumarashutosh, there is a conflict with the develop branch. Could you update the PR? 😁 |
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
c618ab3 to
040bc19
Compare
Handwritten Parser Foundation
This PR introduces a handwritten TypeScript parser implementation to replace the PeggyJS-generated parser, building it up incrementally while maintaining AST compatibility with the existing grammar.
What's Added
parser.ts— core handwritten parser with block dispatch and inline parsing loopscanner.ts— cursor abstraction withposition()/backtrack()for backtrackingchars.ts— character classification helpers (isAlpha, isDigit, isPlainChar, etc.) and the emoticon tableutils.tsfor node generation, and types fromdefinitions.tsindex.tsParsing Features Implemented
paragraphnodelineBreaknodes; trailing newlines are swallowed\*,\_,\~,\`,\#,\.codewith no markup parsing inside*text*and**text**with whitespace-only and triple-asterisk edge cases_text_and__text__with word-boundary guards to protectsnake_case~text~and~~text~~with whitespace-only and triple-tilde edge casesskipBold,skipItalic,skipStrike) mirroring PeggyJS skip flags#through####prefix with required space, inline content inside```with optional language tag, raw content (no markup inside)@usernamesupporting special characters like.-_:@in names#channelwith word-boundary guard>prefixed lines with inline markup support inside||text||inline and multi-line||\n...\n||block support[title](url)and<url|title>-/*and1.markers with inline content per item; asterisk bullets respect the grammar's trailing-*guard (so* *,* Hello*stay inline)tldts:emoji:parsing, plus big-emoji handling for shortcode-only messageslocal@domain→mailto:link, with TLD validation andmailto:prefix parts+number→tel:link viaphoneChecker, with prefix/grouping variants<t:...>supporting Unixtime, ISO-8601 (with/without milliseconds and timezone), relativeHH:MM[:SS]times, and thet/T/d/D/f/F/Rformat specifiers:),:D, etc. via a symbol→shortcode table with longest-match and word-boundary dispatch, integrated into big-emoji detectioncolor:#rgb/rgba/rrggbb/rrggbbaawhen thecolorsoption is enabled😀,❤️) including ZWJ sequences, skin-tone modifiers, and variation selectors, both inline and in big-emoji detection---(three or more dashes);***/___stay as emphasis- [ ]/- [x]checklist lines grouped into onetasksnodeTest Progress
Benchmarks
Throughput: Hand-written parser vs. PeggyJS-generated parser (ops/sec)
Plain Text
Emphasis / Formatting
URLs & Links
Emoji
Mentions
Code
Structured Blocks
KaTeX (Math)
Adversarial / Stress
Real-World Messages
Timestamps
Goal
Notes
Summary by CodeRabbit
New Features
Refactor