Skip to content

Commit 0e30b6e

Browse files
committed
Merge remote-tracking branch 'origin/main' into refactor/ai-assistant-mechanics
2 parents d2af22a + d1e6743 commit 0e30b6e

16 files changed

Lines changed: 1590 additions & 224 deletions

File tree

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
---
2+
name: review-change
3+
description: Review a code change against QuestDB Web Console coding standards
4+
argument-hint: [PR number, PR URL, commit hash, unstaged changes, staged changes]
5+
allowed-tools: Bash(gh *), Bash(yarn test:unit), Bash(yarn lint), Bash(yarn typecheck), Bash(yarn build), Read, Grep, Glob, Agent
6+
---
7+
8+
Review `$ARGUMENTS`
9+
10+
## Review mindset
11+
12+
You are a senior frontend engineer performing a blocking code review on the QuestDB Web Console. This is the primary UI for a mission-critical time-series database. Any bug could result in data loss or misconceptions about the data. Be critical, thorough, and opinionated. Your job is to catch problems before they ship, not to be nice.
13+
14+
- **Assume nothing is correct until you've verified it.** Read surrounding code to understand context — don't just look at the diff in isolation.
15+
- **Flag every issue you find**, no matter how small. Do not soften language or hedge. Say "this is wrong" not "this might be an issue".
16+
- **Do not praise the code.** Skip "looks good", "nice work", "clever approach". Focus entirely on problems and risks.
17+
- **Think adversarially.** For each change, ask: what happens with an empty result set? What if the API returns an error? What if the user clicks/presses multiple times? What if the user interacts with multiple elements consecutively? What if the component unmounts mid-request? What if page refresh happens during the operation? What if the theme changes? What if the browser tab is backgrounded? What if database configuration/settings changes?
18+
- **Check what's missing**, not just what's there. Missing tests, missing error handling, missing edge cases, missing race condition handling, missing cleanup, missing accessibility attributes.
19+
- **Verify every claim.** If the PR title says "fix", verify the bug actually existed and the fix is correct. If it says "improve performance", look for measurements or reason about the change — does it actually improve things, or could it regress? If it says "simplify", verify the new code is actually simpler and doesn't drop behavior. Treat the PR description as an unverified hypothesis, not a statement of fact.
20+
- **Read the full context of changed files** when the diff alone is ambiguous. Use Read/Grep/Glob to inspect the surrounding code, callers, event handlers, and related tests.
21+
- **Assess reachability before reporting.** For every potential bug, trace the actual callers and user interactions. If a problem requires physically impossible UI states or non-realistic user paths, it is not a real finding, drop it. Focus on bugs that real user interactions can trigger.
22+
23+
## Step 1: Gather PR/Diff context
24+
25+
Fetch PR/Diff metadata, and any review comments:
26+
27+
```bash
28+
gh pr view $ARGUMENTS --json number,title,body,labels,state
29+
gh pr diff $ARGUMENTS
30+
gh pr view $ARGUMENTS --comments
31+
```
32+
33+
If the user mentions reviewing only staged diff, or unstaged diff, only review the mentioned part, not something else.
34+
35+
## Step 2: PR title and description
36+
37+
Check against conventions:
38+
- Title follows Conventional Commits: `type: description`
39+
- Description repeats the verb (e.g., `fix: fix ...` not `fix: grid column ...`)
40+
- Description speaks to end-user impact, not implementation internals
41+
42+
## Step 3a: Parallel review
43+
44+
You are the main agent, and your task is to manage the subagents, not diving into the code initially. Launch the following subagents in parallel for dedicated review topics. Each subagent receives the full PR/diff and should read surrounding source files as needed for context.
45+
46+
**Agent 1: React correctness & hooks:** Hook rules violations, stale closures, missing or incorrect dependency arrays, unnecessary stable references in deps array, missing useEffect cleanup (timers, subscriptions, AbortControllers, event handlers), conditional hook calls, unnecessary usage of setState+useEffect combination where event handler could be utilized, state updates after unmount, incorrect use of refs, broken controlled/uncontrolled component patterns, incorrect key props causing lost state, event handler reference stability, unnecessary RAF usage, unnecessary layout effect usage.
47+
48+
**Agent 2: Component usage and code splitting:** Unnecessarily long component definitions without splitting into subcomponents, improper folder structure, defining the same function component/styled component in multiple places, defining complex logic inside the component without using a proper `utils` file, prop drilling where context would be cleaner, creating a new component while an existing component could be utilized, plain button/flex div usage where `Button` and `Box` could be utilized.
49+
50+
**Agent 3: Readability and maintainability:**
51+
Ambiguous naming of variables and components, missing early returns, detection with regex usage (regex usage is discouraged if there's a better way), unnecessary comments for trivial logic, logic inside render function, unnecessary IIFE usage, unnecessary `!` non-null assertions, unnecessary `?.` optional chain, unnecessary optional fields (`?:`) that cannot be null/undefined, overly broad types that could be extracted to discriminated unions.
52+
53+
**Agent 4: Performance:** Unnecessary rerenders through missing useMemo/useCallback where a component passes callbacks to memoized children or large lists, unnecessary memoization of small functions or computations which does not prevent any rerenders, unnecessary network requests, unnecessarily frequent network requests, frequent IndexedDB updates, inline object/array/function creation in JSX props causing referential inequality, expensive computations without memoization, missing virtualization for large lists.
54+
55+
**Agent 5: Styling & theming:** Hardcoded colors/sizes instead of theme tokens, CSS specificity issues, z-index conflicts, animation performance (prefer `transform`/`opacity` over layout-triggering properties), styled-components created inside render functions (causes remounting), proper use of `css` helper for conditional styles, `$`-prefixed prop names for style-only props, proper use of `rem` units, not pixels, proper use of styled components instead of inline styling, proper use of existing icon libraries instead of custom SVGs, proper font/icon/box sizes that are consistent.
56+
57+
**Agent 6: State management & async:** Redux action/reducer correctness, immutable state updates (no direct mutation), missing error handling in async operations, race conditions in concurrent API calls, missing AbortController usage for cancellable requests, stale data after navigation, proper use of the EventBus pattern, missing loading/error states in UI, unnecessary setTimeout calls just to trick the event loop.
58+
59+
**Agent 7: Test review & coverage:** User path coverage with E2E tests for new/modified flows (e2e/tests/**/*.spec.js using Cypress), unit test coverage for complex utility functions.
60+
61+
**Agent 8: Accessibility & UX:** Missing ARIA labels on interactive elements, missing keyboard navigation support, focus management issues, missing alt text on images, color contrast concerns, screen reader compatibility, click handlers without keyboard equivalents, missing error announcements for assistive technology, broken tab order.
62+
63+
**Agent 9: Browser compatibility and security**: No reliance on APIs unavailable in target browsers without polyfills, no CSS properties with limited availability across different browsers, no exposure to XSS vectors, no SQL injection risk, no open redirects via user-controlled URLs.
64+
65+
66+
## Step 3b: Fixed quality checks
67+
While the subagents are scanning the code for their tasks, you will perform predefined quality checks on the code.
68+
- Type errors: `yarn typecheck`
69+
- Build failure: `yarn build`
70+
- Lint errors: `yarn lint`
71+
- Unit test failures: `yarn test:unit`
72+
After performing these checks, if there are errors/failures, add the errors from these checks to the output table at the end, one row for each check. Build, type, and test failures are critical, lint errors are moderate.
73+
After completing this step, you will wait for subagent results.
74+
75+
## Step 3c: Verify every subagent finding against source code
76+
77+
Combine all agent findings into a single deduplicated **draft** report. Do NOT present this draft to the user yet — it goes straight into verification. The parallel review agents work from the diff alone and frequently produce false positives. Every finding MUST be verified before it is reported.
78+
79+
For each finding in the draft report:
80+
81+
1. **Read the actual source code** at the exact lines cited. Do not rely on the agent's description alone.
82+
2. **Trace the full code path**: See if the code path verifies the claim, and the issue can occur with realistic user actions. If possible, run the code with `node -e` to see if the claim is true.
83+
3. **Think about the use case**: Check if the issue really creates regression for the user, considering the usage patterns. Follow the steps to reproduce by reading the relevant code. If the issue cannot be reproduced under a realistic user scenario, this issue cannot be critical.
84+
4. **Classify each finding** as:
85+
- **CONFIRMED** — the bug is real and reproducible via the traced code path
86+
- **FALSE POSITIVE** — the code is actually correct (explain why)
87+
- **CONFIRMED with nuance** — the issue exists but is less severe than stated (explain)
88+
89+
**Move false positives to a separate "Downgraded" section** at the end of the report. For each, give a one-line explanation of why it was dismissed. This lets the PR author verify the reasoning and catch verification mistakes.
90+
91+
Launch verification agents in parallel where findings are independent. Each verification agent should read surrounding source files, not just the diff.
92+
93+
## Step 4: Output
94+
You will provide all the information three sections: `## Issues`, `## False-positives`, `## Summary`:
95+
96+
### Issues section
97+
Present the validated findings in a table with the following columns:
98+
- Issue ID (#1, #2 etc.)
99+
- Issue name (3-5 words)
100+
- Category: "Quality check" | title of the subagent (the task name)
101+
- Severity: "Critical" | "Moderate" | "Minor"
102+
- Description: Full user impact in a plain, small paragraph
103+
- Steps to reproduce: Clear list with bullet points, the user path results in the issue
104+
- Suggested fix
105+
106+
### False-positives section
107+
Provide the list of false-positive findings from subagents that you verified that the issue does not exist, with the following fields:
108+
- Category: title of the subagent (the task name)
109+
- Description: the description from subagent
110+
- Explanation: your explanation on why it's a false-positive
111+
112+
### Summary section
113+
- One-line verdict: approve, request changes, or needs discussion
114+
- Highlight any regressions or tradeoffs
115+
- State how many draft findings were verified vs dropped as false positives (e.g., "8 findings verified, 4 false positives removed")

e2e/commands.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ const materializedViewSchemas = {
3333
btc_trades_mv:
3434
"CREATE MATERIALIZED VIEW IF NOT EXISTS btc_trades_mv WITH BASE btc_trades as (" +
3535
"SELECT timestamp, avg(amount) avg FROM btc_trades SAMPLE BY 1m) PARTITION BY week;",
36+
btc_trades_mv_on_mv:
37+
"CREATE MATERIALIZED VIEW IF NOT EXISTS btc_trades_mv_on_mv as (" +
38+
"SELECT timestamp, avg(avg) avg FROM btc_trades_mv SAMPLE BY 1h) PARTITION BY week;",
3639
}
3740

3841
const viewSchemas = {

e2e/tests/console/editor.spec.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,14 @@ describe("appendQuery", () => {
554554
cy.getEditorContent().should("have.value", expected)
555555
cy.getSelectedLines().should("have.length", 1)
556556
})
557+
558+
it("should inject a semicolon when the existing query is unterminated", () => {
559+
cy.typeQuery(`select 1`)
560+
cy.selectQuery(0)
561+
const expected = `select 1;\n${queries[0]}`
562+
cy.getEditorContent().should("have.value", expected)
563+
cy.getSelectedLines().should("have.length", 1)
564+
})
557565
})
558566

559567
describe("&query URL param", () => {

e2e/tests/console/schema.spec.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,3 +692,99 @@ describe("materialized views", () => {
692692
})
693693
})
694694
})
695+
696+
describe("create materialized view from context menu", () => {
697+
const sourceTable = "btc_trades"
698+
const nonWalTable = "btc_trades_no_wal"
699+
const nonPartitionedTable = "my_publics"
700+
// btc_trades is PARTITION BY DAY → derived SAMPLE BY 1h → view name btc_trades_1h.
701+
const generatedMatView = "btc_trades_1h"
702+
703+
before(() => {
704+
cy.loadConsoleWithAuth()
705+
cy.createTable(sourceTable)
706+
cy.createTable(nonWalTable)
707+
cy.createTable(nonPartitionedTable)
708+
cy.refreshSchema()
709+
})
710+
711+
after(() => {
712+
cy.loadConsoleWithAuth()
713+
cy.dropMaterializedView(generatedMatView)
714+
cy.dropTableIfExists(sourceTable)
715+
cy.dropTableIfExists(nonWalTable)
716+
cy.dropTableIfExists(nonPartitionedTable)
717+
})
718+
719+
it("disables the menu item for non-WAL and non-partitioned tables, and generates a runnable matview DDL from a valid source", () => {
720+
cy.getByDataHook("schema-table-title").contains(nonWalTable).rightclick()
721+
cy.getByDataHook("table-context-menu-create-matview").should(
722+
"have.attr",
723+
"data-disabled",
724+
)
725+
cy.realPress("Escape")
726+
727+
cy.getByDataHook("schema-table-title")
728+
.contains(nonPartitionedTable)
729+
.rightclick()
730+
cy.getByDataHook("table-context-menu-create-matview").should(
731+
"have.attr",
732+
"data-disabled",
733+
)
734+
cy.realPress("Escape")
735+
736+
cy.clearEditor()
737+
cy.getByDataHook("schema-table-title").contains(sourceTable).rightclick()
738+
cy.getByDataHook("table-context-menu-create-matview")
739+
.should("not.be.disabled")
740+
.click()
741+
742+
cy.getEditorContent().should("contain.value", "CREATE MATERIALIZED VIEW")
743+
cy.runLine().clearEditor()
744+
745+
cy.refreshSchema()
746+
cy.expandMatViews()
747+
cy.getByDataHook("schema-matview-title").should("contain", generatedMatView)
748+
})
749+
})
750+
751+
describe("create materialized view from matview context menu", () => {
752+
const sourceTable = "btc_trades"
753+
const sourceMatView = "btc_trades_mv"
754+
// btc_trades_mv is SAMPLE BY 1m → next rung 5m; name has no period token,
755+
// so the generator appends `_5m`.
756+
const generatedMatView = "btc_trades_mv_5m"
757+
758+
before(() => {
759+
cy.loadConsoleWithAuth()
760+
cy.createTable(sourceTable)
761+
cy.createMaterializedView(sourceMatView)
762+
cy.refreshSchema()
763+
})
764+
765+
after(() => {
766+
cy.loadConsoleWithAuth()
767+
cy.dropMaterializedView(generatedMatView)
768+
cy.dropMaterializedView(sourceMatView)
769+
cy.dropTableIfExists(sourceTable)
770+
})
771+
772+
it("generates a runnable chained matview DDL from a matview source", () => {
773+
cy.expandMatViews()
774+
775+
cy.clearEditor()
776+
cy.getByDataHook("schema-matview-title")
777+
.contains(sourceMatView)
778+
.rightclick()
779+
cy.getByDataHook("table-context-menu-create-matview")
780+
.should("not.be.disabled")
781+
.click()
782+
783+
cy.getEditorContent().should("contain.value", "CREATE MATERIALIZED VIEW")
784+
cy.runLine().clearEditor()
785+
786+
cy.refreshSchema()
787+
cy.expandMatViews()
788+
cy.getByDataHook("schema-matview-title").should("contain", generatedMatView)
789+
})
790+
})

e2e/tests/console/tableDetails.spec.js

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ const {
1212
const TEST_TABLE = "btc_trades"
1313
const TEST_TABLE_NO_WAL = "btc_trades_no_wal"
1414
const TEST_MATVIEW = "btc_trades_mv"
15+
const TEST_MATVIEW_ON_MV = "btc_trades_mv_on_mv"
1516
const TEST_VIEW = "btc_trades_view"
1617

1718
function interceptTablesQuery(modifications) {
@@ -637,6 +638,53 @@ describe("TableDetailsDrawer", () => {
637638
})
638639
})
639640

641+
describe("materialized view based on another materialized view", () => {
642+
before(() => {
643+
cy.loadConsoleWithAuth()
644+
cy.createTable(TEST_TABLE)
645+
cy.createMaterializedView(TEST_MATVIEW)
646+
cy.createMaterializedView(TEST_MATVIEW_ON_MV)
647+
})
648+
649+
beforeEach(() => {
650+
cy.loadConsoleWithAuth()
651+
cy.refreshSchema()
652+
cy.expandMatViews()
653+
})
654+
655+
it("should open as matview and navigate to a matview base table preserving the matview kind", () => {
656+
cy.openDetailsDrawer(TEST_MATVIEW_ON_MV, "matview")
657+
658+
cy.getByDataHook("table-details-type-badge").should(
659+
"contain",
660+
"Materialized View",
661+
)
662+
663+
cy.getByDataHook("table-details-tab-details").click()
664+
665+
cy.getByDataHook("table-details-base-table-section").should("be.visible")
666+
cy.getByDataHook("table-details-base-table-link").should(
667+
"contain",
668+
TEST_MATVIEW,
669+
)
670+
671+
cy.getByDataHook("table-details-base-table-link").click()
672+
673+
cy.getByDataHook("table-details-name").should("have.value", TEST_MATVIEW)
674+
cy.getByDataHook("table-details-type-badge").should(
675+
"contain",
676+
"Materialized View",
677+
)
678+
})
679+
680+
after(() => {
681+
cy.loadConsoleWithAuth()
682+
cy.dropMaterializedView(TEST_MATVIEW_ON_MV)
683+
cy.dropMaterializedView(TEST_MATVIEW)
684+
cy.dropTable(TEST_TABLE)
685+
})
686+
})
687+
640688
describe("materialized view invalid state (R2)", () => {
641689
before(() => {
642690
cy.loadConsoleWithAuth()

src/modules/ConsoleEventTracker/events.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export enum ConsoleEvent {
6868
SCHEMA_RESUME_WAL_SUBMIT = "schema.resume_wal_submit",
6969
SCHEMA_CONTEXT_COPY_DDL = "schema.context_copy_ddl",
7070
SCHEMA_CONTEXT_EXPLAIN = "schema.context_explain",
71+
SCHEMA_CONTEXT_CREATE_MATVIEW = "schema.context_create_matview",
7172
SCHEMA_COPY_MULTIPLE = "schema.copy_multiple",
7273

7374
TABLE_DETAILS_TAB_SWITCH = "table_details.tab_switch",

src/providers/EditorProvider/index.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import React, {
1212
} from "react"
1313
import {
1414
appendQuery,
15-
AppendQueryOptions,
1615
clearModelMarkers,
1716
insertTextAtCursor,
1817
QuestDBLanguageName,
@@ -76,7 +75,7 @@ export type EditorContext = {
7675
editorRef: MutableRefObject<IStandaloneCodeEditor | null>
7776
monacoRef: MutableRefObject<Monaco | null>
7877
insertTextAtCursor: (text: string) => void
79-
appendQuery: (query: string, options?: AppendQueryOptions) => void
78+
appendQuery: (query: string) => void
8079
tabsDisabled: boolean
8180
setTabsDisabled: (disabled: boolean) => void
8281
buffers: Buffer[]
@@ -726,9 +725,9 @@ export const EditorProvider: React.FC = ({ children }) => {
726725
insertTextAtCursor(editorRef.current, text)
727726
}
728727
},
729-
appendQuery: (text, options) => {
728+
appendQuery: (text) => {
730729
if (editorRef?.current) {
731-
appendQuery(editorRef.current, text, options)
730+
appendQuery(editorRef.current, text)
732731
}
733732
},
734733
tabsDisabled,

src/scenes/Console/import.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ export const Import = () => (
1616
eventBus.publish(EventType.MSG_QUERY_SCHEMA)
1717
eventBus.publish(EventType.MSG_QUERY_FIND_N_EXEC, {
1818
query: `"${result.location}"`,
19-
options: { appendAt: "end" },
2019
})
2120
}
2221
}}

src/scenes/Editor/Monaco/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ const MonacoEditor = ({ hidden = false }: { hidden?: boolean }) => {
12051205
})
12061206
// otherwise, append the query
12071207
} else {
1208-
appendQuery(editor, trimmedQuery, { appendAt: "end" })
1208+
appendQuery(editor, trimmedQuery)
12091209
const newValue = editor.getValue()
12101210
void updateBuffer(activeBuffer.id as number, { value: newValue })
12111211
}

src/scenes/Editor/Monaco/legacy-event-bus.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
* limitations under the License.
2222
*
2323
******************************************************************************/
24-
import { appendQuery, AppendQueryOptions } from "./utils"
24+
import { appendQuery } from "./utils"
2525
import { eventBus } from "../../../modules/EventBus"
2626
import { EventType } from "../../../modules/EventBus/types"
2727
import type { editor } from "monaco-editor"
@@ -36,12 +36,12 @@ export const registerLegacyEventBusEvents = ({
3636
toggleRunning: (runningType?: RunningType) => void
3737
getRunning: () => RunningType
3838
}) => {
39-
eventBus.subscribe<{ query: string; options?: AppendQueryOptions }>(
39+
eventBus.subscribe<{ query: string }>(
4040
EventType.MSG_QUERY_FIND_N_EXEC,
4141
(payload) => {
4242
if (payload) {
4343
const text = `${payload.query};`
44-
appendQuery(editor, text, payload.options)
44+
appendQuery(editor, text)
4545
if (getRunning() !== RunningType.NONE) return
4646
toggleRunning()
4747
}

0 commit comments

Comments
 (0)