-
Notifications
You must be signed in to change notification settings - Fork 31
add left toolbar and show the graph schema #80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
f13439a
a903492
a291ef3
4fc9370
3831b87
06e15c9
a41abbb
e85af34
7f036ec
1ddb0bd
bf70daa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -49,6 +49,12 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Sanitize the query to prevent injection attacks.""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return query.replace('\n', ' ').replace('\r', ' ')[:500] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def sanitize_log_input(value: str) -> str: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Sanitize input for safe logging (remove newlines and carriage returns).""" | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not isinstance(value, str): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return str(value) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return value.replace('\n', ' ').replace('\r', ' ') | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @graphs_bp.route("") | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @token_required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def list_graphs(): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -63,6 +69,105 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return jsonify(filtered_graphs) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @graphs_bp.route("/<string:graph_id>/data", methods=["GET"]) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @token_required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_graph_data(graph_id: str): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """Return all nodes and edges for the specified graph (namespaced to the user). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| This endpoint returns a JSON object with two keys: `nodes` and `edges`. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Nodes contain a minimal set of properties (id, name, labels, props). | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Edges contain source and target node names (or internal ids), type and props. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not graph_id or not isinstance(graph_id, str): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return jsonify({"error": "Invalid graph_id"}), 400 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| graph_id = graph_id.strip()[:200] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| namespaced = g.user_id + "_" + graph_id | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| graph = db.select_graph(namespaced) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.error("Failed to select graph %s: %s", sanitize_log_input(namespaced), e) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failureCode scanning / CodeQL Log Injection High
This log entry depends on a
user-provided value Error loading related location Loading
Copilot AutofixAI 6 months ago To fix the log injection vulnerability, we should improve the Required changes:
Suggested changeset
1
api/routes/graphs.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return jsonify({"error": "Graph not found or database error"}), 404 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+87
to
+92
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle missing graph object and harden logging (avoid None deref + log forging finding)
try:
- graph = db.select_graph(namespaced)
- except Exception as e:
- logging.error("Failed to select graph %s: %s", sanitize_log_input(namespaced), e)
- return jsonify({"error": "Graph not found or database error"}), 404
+ graph = db.select_graph(namespaced)
+ except Exception as e:
+ logging.error("Failed to select graph %r: %s", namespaced, e, exc_info=True)
+ return jsonify({"error": "Database error selecting graph"}), 500
+ if not graph:
+ logging.info("Graph not found: %r", namespaced)
+ return jsonify({"error": "Graph not found"}), 404Also applies to: 93-106 🧰 Tools🪛 GitHub Check: CodeQL[failure] 90-90: Log Injection 🤖 Prompt for AI Agents |
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Build table nodes with columns and table-to-table links (foreign keys) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tables_query = """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MATCH (t:Table) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| OPTIONAL MATCH (c:Column)-[:BELONGS_TO]->(t) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN t.name AS table, collect(DISTINCT {name: c.name, type: c.type}) AS columns | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| links_query = """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MATCH (src_col:Column)-[:BELONGS_TO]->(src_table:Table), | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (tgt_col:Column)-[:BELONGS_TO]->(tgt_table:Table), | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (src_col)-[:REFERENCES]->(tgt_col) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| RETURN DISTINCT src_table.name AS source, tgt_table.name AS target | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| tables_res = graph.query(tables_query).result_set | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| links_res = graph.query(links_query).result_set | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception as e: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logging.error("Error querying graph data for %s: %s", sanitize_log_input(namespaced), e) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Check failureCode scanning / CodeQL Log Injection High
This log entry depends on a
user-provided value Error loading related location Loading
Copilot AutofixAI 6 months ago To fix the log injection vulnerability, we should enhance the
Suggested changeset
1
api/routes/graphs.py
Copilot is powered by AI and may make mistakes. Always verify output.
Refresh and try again.
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return jsonify({"error": "Failed to read graph data"}), 500 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodes = [] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for row in tables_res: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| table_name, columns = row | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Normalize columns: ensure a list of dicts with name/type | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not isinstance(columns, list): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| columns = [] if columns is None else [columns] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| normalized = [] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for col in columns: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # col may be a mapping-like object or a simple value | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not col: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Some drivers may return a tuple or list for the collected map | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if isinstance(col, (list, tuple)) and len(col) >= 2: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # try to interpret as (name, type) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = col[0] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctype = col[1] if len(col) > 1 else None | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| elif isinstance(col, dict): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = col.get('name') or col.get('columnName') | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctype = col.get('type') or col.get('dataType') | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| name = str(col) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ctype = None | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not name: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| normalized.append({"name": name, "type": ctype}) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| nodes.append({ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "id": table_name, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "name": table_name, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "columns": normalized, | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| links = [] | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seen = set() | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for row in links_res: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| source, target = row | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| key = (source, target) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if key in seen: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seen.add(key) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| links.append({"source": source, "target": target}) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return jsonify({"nodes": nodes, "links": links}) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @graphs_bp.route("", methods=["POST"]) | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @token_required | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def load_graph(): | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -137,16 +137,15 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* Theme Toggle Button Styles */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #theme-toggle-btn { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: fixed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top: 20px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right: 90px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: static; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* GitHub Link Button Styles */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #github-link-btn { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: fixed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top: 20px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right: 150px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| right: 90px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 4px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -295,3 +294,89 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| box-shadow: none; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* Left vertical toolbar (activity bar style) */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* Left vertical toolbar (activity bar style) */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #left-toolbar { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| position: fixed; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top: 0px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bottom: 0px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 48px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| left: 0px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: var(--bg-tertiary, rgba(255,255,255,0.02)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| /* border-radius: 10px; */ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 6px 6px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| z-index: 1050; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| justify-content: flex-start; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| box-shadow: 0 6px 18px rgba(0,0,0,0.25); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border: 1px solid var(--border-color, rgba(255,255,255,0.03)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| backdrop-filter: blur(6px); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| overflow: hidden; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #left-toolbar-inner { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 10px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toolbar-button { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 40px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height: 40px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| justify-content: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| box-sizing: border-box; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+329
to
+337
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Improve affordance and focus visibility for toolbar buttons. Add pointer cursor, normalize border/background, and provide a visible focus indicator for keyboard users. Apply this diff: .toolbar-button {
width: 40px;
height: 40px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
+ cursor: pointer;
+ border: none;
+ background: transparent;
}
+
+.toolbar-button:focus-visible {
+ outline: 2px solid var(--falkor-accent);
+ outline-offset: 2px;
+}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toolbar-button svg { color: var(--text-primary); opacity: 0.95; width:18px; height:18px } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toolbar-button:hover { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: translateY(-4px) scale(1.03); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: linear-gradient(180deg, rgba(255,255,255,0.02), rgba(255,255,255,0.01)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .toolbar-button[aria-pressed="true"] { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| background: var(--falkor-accent, rgba(150,120,220,0.22)); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| transform: translateY(-4px) scale(1.03); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #toolbar-buttons { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| display: flex; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex-direction: column; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| gap: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 6px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #left-toolbar-footer { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| margin-top: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: 100%; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height: 6px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex-shrink: 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @media (max-width: 768px) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #left-toolbar { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| left: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| top: 12px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| bottom: auto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| width: auto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| height: 48px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| padding: 4px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| border-radius: 8px; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| flex-direction: row; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| align-items: center; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #left-toolbar-inner { flex-direction: row; gap: 6px; } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -19,28 +19,20 @@ | |||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /* Menu Responsive */ | ||||||||||||||||||||||||
| @media (max-width: 768px) { | ||||||||||||||||||||||||
| #menu-container { | ||||||||||||||||||||||||
| .sidebar-container { | ||||||||||||||||||||||||
| position: fixed; | ||||||||||||||||||||||||
| top: 0; | ||||||||||||||||||||||||
| left: 0; | ||||||||||||||||||||||||
| height: 100vh; | ||||||||||||||||||||||||
| z-index: 999; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| #menu-container.open { | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| .sidebar-container.open { | ||||||||||||||||||||||||
| width: 80vw; | ||||||||||||||||||||||||
| padding: 15px; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
Comment on lines
20
to
34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainRepo-wide cleanup after selector rename. To prevent similar regressions, grep for leftover #menu-container, #side-menu-button, and #theme-toggle-btn occurrences across CSS/JS/templates and update/remove them. 🏁 Script executed: #!/bin/bash
# Find stale selectors/ids after the toolbar/sidebar refactor
set -euo pipefail
echo "Searching for legacy selectors..."
rg -n -S -C2 --glob 'api/**' '#menu-container|#side-menu-button|#theme-toggle-btn'
echo "Check for sidebar open-state rules that may need mobile overrides..."
rg -n -S -C2 --glob 'api/**' '\.sidebar-container\.open'Length of output: 1955 Cleanup legacy selectors and ensure consistent sidebar styles The refactor renamed menu and toolbar elements, but the following leftover selectors and duplicate styles remain and should be removed or updated:
Additionally, there’s a duplicate 🤖 Prompt for AI Agents
Comment on lines
+30
to
34
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Explicitly override desktop open-state offset on mobile. Cross-file context notes .sidebar-container.open has left: 48px in menu.css. Without overriding here, the drawer may be shifted on mobile. Set left: 0 in the mobile open state to ensure a true overlay. .sidebar-container.open {
+ left: 0;
width: 80vw;
padding: 15px;
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /* Side Menu Button Responsive */ | ||||||||||||||||||||||||
| @media (max-width: 768px) { | ||||||||||||||||||||||||
| #side-menu-button { | ||||||||||||||||||||||||
| top: 15px; | ||||||||||||||||||||||||
| left: 15px; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| /* Chat Messages Responsive */ | ||||||||||||||||||||||||
| @media (max-width: 768px) { | ||||||||||||||||||||||||
| .message { | ||||||||||||||||||||||||
|
|
@@ -129,14 +121,9 @@ | |||||||||||||||||||||||
| height: 40px; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| #theme-toggle-btn { | ||||||||||||||||||||||||
| top: 15px; | ||||||||||||||||||||||||
| right: 80px; | ||||||||||||||||||||||||
| } | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
| #github-link-btn { | ||||||||||||||||||||||||
| top: 15px; | ||||||||||||||||||||||||
| right: 135px; | ||||||||||||||||||||||||
| right: 60px; | ||||||||||||||||||||||||
| padding: 6px 8px; | ||||||||||||||||||||||||
| font-size: 12px; | ||||||||||||||||||||||||
| height: 40px; | ||||||||||||||||||||||||
|
|
||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.