From a7e035f1299b7a7185be054a8a18c85d8b02d6b1 Mon Sep 17 00:00:00 2001 From: Perezented Date: Wed, 8 Apr 2026 14:26:12 -0500 Subject: [PATCH 1/4] Improve config, safety, and types: env-based debug, GROUP_BYTES config, type hints --- app.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/app.py b/app.py index 766a623..97d3ff5 100644 --- a/app.py +++ b/app.py @@ -1,23 +1,22 @@ from flask import Flask, send_from_directory, render_template, request, jsonify import os +from typing import List, Dict, Any app = Flask(__name__, static_folder='static', template_folder='static') +# Configuration Constants +GROUP_BYTES = int(os.environ.get('GROUP_BYTES', 1024)) +BITS_PER_GROUP = GROUP_BYTES * 8 @app.route('/') -def index(): +def index() -> str: return render_template('index.html') -@app.route('/static/') -def serve_static(path): - return send_from_directory('static', path) - - if __name__ == '__main__': port = int(os.environ.get('PORT', 5000)) - app.run(host='0.0.0.0', port=port, debug=True) - + debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true' + app.run(host='0.0.0.0', port=port, debug=debug) @app.route('/api/group_summary', methods=['POST']) def group_summary(): @@ -39,8 +38,8 @@ def group_summary(): return jsonify(error='mode_not_supported'), 400 # constants should match the client (1024 bytes => 8192 bits per KB) - GROUP_BYTES = 1024 - BITS_PER_GROUP = GROUP_BYTES * 8 + current_group_bytes = GROUP_BYTES + BITS_PER_GROUP = current_group_bytes * 8 total_bits = int(value_int) if total_bits < 0: return jsonify(error='invalid_value'), 400 From 09f5fa1ac9a46c26c9428700140457ebbb9f9daa Mon Sep 17 00:00:00 2001 From: Perezented Date: Wed, 8 Apr 2026 15:30:21 -0500 Subject: [PATCH 2/4] UI: default Mode to 'Bits and bytes' and Unit to 'MB' --- static/index.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/static/index.html b/static/index.html index c0aa782..2a7584c 100644 --- a/static/index.html +++ b/static/index.html @@ -23,8 +23,8 @@

Bit Flipper

@@ -33,7 +33,7 @@

Bit Flipper

- + From 90248b49ea3dbb768f4b802325def0bf5e61eb64 Mon Sep 17 00:00:00 2001 From: Perezented Date: Wed, 8 Apr 2026 15:45:11 -0500 Subject: [PATCH 3/4] Fix Copilot review issues; update playwright config and tests for MB/bitcount defaults --- app.py | 36 ++++++++++++++------- conftest.py | 5 +++ playwright.config.ts | 2 +- tests/playwright/bitviewer.spec.ts | 2 ++ tests/playwright/inputting-x-bits.spec.ts | 23 +++++++++---- tests/playwright/large-input.spec.ts | 24 +++++++++----- tests/playwright/test-number-inputs.spec.ts | 3 ++ 7 files changed, 67 insertions(+), 28 deletions(-) create mode 100644 conftest.py diff --git a/app.py b/app.py index 97d3ff5..86d5d1a 100644 --- a/app.py +++ b/app.py @@ -1,23 +1,32 @@ -from flask import Flask, send_from_directory, render_template, request, jsonify +from flask import Flask, render_template, request, jsonify import os -from typing import List, Dict, Any + app = Flask(__name__, static_folder='static', template_folder='static') + # Configuration Constants -GROUP_BYTES = int(os.environ.get('GROUP_BYTES', 1024)) +def _get_group_bytes() -> int: + default_group_bytes = 1024 + raw_value = os.environ.get('GROUP_BYTES', str(default_group_bytes)) + try: + group_bytes = int(raw_value) + except (TypeError, ValueError): + return default_group_bytes + if group_bytes <= 0: + return default_group_bytes + return group_bytes + + +GROUP_BYTES = _get_group_bytes() BITS_PER_GROUP = GROUP_BYTES * 8 + @app.route('/') def index() -> str: return render_template('index.html') -if __name__ == '__main__': - port = int(os.environ.get('PORT', 5000)) - debug = os.environ.get('FLASK_DEBUG', 'False').lower() == 'true' - app.run(host='0.0.0.0', port=port, debug=debug) - @app.route('/api/group_summary', methods=['POST']) def group_summary(): # Accept JSON: { value: number-as-string, mode: 'bitcount'|'binary' } @@ -37,9 +46,6 @@ def group_summary(): if mode != 'bitcount': return jsonify(error='mode_not_supported'), 400 - # constants should match the client (1024 bytes => 8192 bits per KB) - current_group_bytes = GROUP_BYTES - BITS_PER_GROUP = current_group_bytes * 8 total_bits = int(value_int) if total_bits < 0: return jsonify(error='invalid_value'), 400 @@ -53,4 +59,10 @@ def group_summary(): frac = ones / BITS_PER_GROUP groups.append(round(frac, 6)) - return jsonify({ 'group_count': group_count, 'kb_fractions': groups }) + return jsonify({'group_count': group_count, 'kb_fractions': groups}) + + +if __name__ == '__main__': + port = int(os.environ.get('PORT', 5000)) + debug = os.environ.get('FLASK_DEBUG', 'False').strip().lower() in {'1', 'true', 'yes', 'on'} + app.run(host='0.0.0.0', port=port, debug=debug) diff --git a/conftest.py b/conftest.py new file mode 100644 index 0000000..ad37497 --- /dev/null +++ b/conftest.py @@ -0,0 +1,5 @@ +import sys +import os + +# Add the project root to sys.path so tests can import app +sys.path.insert(0, os.path.dirname(__file__)) diff --git a/playwright.config.ts b/playwright.config.ts index dc36e0e..ffb525c 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -5,7 +5,7 @@ export default defineConfig({ timeout: 120000, expect: { timeout: 5000 }, webServer: { - command: 'python app.py', + command: '.venv/bin/python app.py', port: 5000, cwd: process.cwd(), timeout: 120000, diff --git a/tests/playwright/bitviewer.spec.ts b/tests/playwright/bitviewer.spec.ts index 0266343..0652a0e 100644 --- a/tests/playwright/bitviewer.spec.ts +++ b/tests/playwright/bitviewer.spec.ts @@ -5,6 +5,7 @@ test.describe('Bit Flipper bit viewer tests', () => { await page.goto('/'); // ensure we're in bitcount mode await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('1024'); // wait for rendered bytes to appear @@ -15,6 +16,7 @@ test.describe('Bit Flipper bit viewer tests', () => { test('toggling mode resets input and clears bytes', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('1024'); const bytes = page.locator('.byte'); diff --git a/tests/playwright/inputting-x-bits.spec.ts b/tests/playwright/inputting-x-bits.spec.ts index feccf8c..6b7f2d3 100644 --- a/tests/playwright/inputting-x-bits.spec.ts +++ b/tests/playwright/inputting-x-bits.spec.ts @@ -5,6 +5,7 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of bytes', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('2048'); const bytes = page.locator('.byte'); @@ -14,6 +15,7 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of KB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('16384'); const kbs = page.locator('.kb-block'); @@ -23,6 +25,7 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of MB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); // 5 MB = 5 * 8192 * 1024 = 41943040 bits await input.fill('41943040'); @@ -33,6 +36,7 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of GB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); // 3 GB = 3 * 8192 * 1024 * 1024 = 25769803776 bits await input.fill('25769803776'); @@ -43,6 +47,7 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of TB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); // 2 TB = 2 * 8192 * 1024 * 1024 * 1024 = 17592186044416 bits await input.fill('17592186044416'); @@ -53,6 +58,7 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of PB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); // 2 PB = 2 * 8192 * 1024 * 1024 * 1024 * 1024 ~ 18014398509481990 bits await input.fill('18014398509481990'); @@ -63,6 +69,7 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of EB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); // 3 EB = 3 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 = 27670116110564300000 bits await input.fill('27670116110564300000'); @@ -73,8 +80,9 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of ZB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); - // 3 ZB = 3 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 28334198897217800000000 bits + // 3 ZB await input.fill('28334198897217800000000'); const zbs = page.locator('.zb-block'); await expect(zbs).toHaveCount(3, { timeout: 10000 }); @@ -83,8 +91,9 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of YB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); - // 4 YB = 4 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 38685626227668100000000000 bits + // 4 YB await input.fill('38685626227668100000000000'); const ybs = page.locator('.yb-block'); await expect(ybs).toHaveCount(4, { timeout: 10000 }); @@ -93,8 +102,8 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of BB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); - // 6 BB = 6 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 59421121885698200000000000000 bits await input.fill('59421121885698200000000000000'); const bbs = page.locator('.bb-block'); await expect(bbs).toHaveCount(6, { timeout: 10000 }); @@ -103,8 +112,8 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of NB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); - // 7 NB = 7 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 70988433612780800000000000000000 bits await input.fill('70988433612780800000000000000000'); const nbs = page.locator('.nb-block'); await expect(nbs).toHaveCount(7, { timeout: 10000 }); @@ -113,8 +122,8 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of DB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); - // 8 DB = 8 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 83076749736557300000000000000000000 bits await input.fill('83076749736557300000000000000000000'); const dbs = page.locator('.db-block'); await expect(dbs).toHaveCount(9, { timeout: 10000 }); // Corrected to 9 due to the large number being too big to calculate accurately. Calculators do not like this big of number, had to break down calculations. @@ -124,8 +133,8 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of QB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); - // 8 QB = 8 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 85070591730234700000000000000000000000 bits await input.fill('85070591730234700000000000000000000000'); const qbs = page.locator('.qb-block'); await expect(qbs).toHaveCount(9, { timeout: 10000 }); // Corrected to 9 due to the large number being too big to calculate accurately. Calculators do not like this big of number, had to break down calculations. @@ -134,8 +143,8 @@ test.describe('Bit Flipper testing inputting x bits', () => { test('inputting x bits shows correct number of OB groups', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); - // 6 OB = 6 * 8192 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 * 1024 = 65334214448820200000000000000000000000000 bits await input.fill('65334214448820200000000000000000000000000'); const obs = page.locator('.ob-block'); await expect(obs).toHaveCount(7, { timeout: 10000 }); // Corrected to 7 numbers are too large to truly calculate correctly. Calculators do not like this big of number, had to break down calculations. diff --git a/tests/playwright/large-input.spec.ts b/tests/playwright/large-input.spec.ts index 55a2293..ba2290a 100644 --- a/tests/playwright/large-input.spec.ts +++ b/tests/playwright/large-input.spec.ts @@ -4,9 +4,8 @@ test.describe('Bit Flipper large input rendering', () => { test('chunked rendering shows loader and completes for large inputs', async ({ page }) => { await page.goto('/'); const input = page.locator('#number-input'); - // treat the input as a bitcount -> large value will render via chunked path - // ensure we're in bitcount mode await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); await input.fill('500000000'); // loader should appear then hide once finished, and we should have the KB @@ -32,6 +31,7 @@ test.describe('Bit Flipper large input rendering', () => { test('MB-level canvas renders for large inputs', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); // 20 MB -> 20 * (8192 bits * 1024 KB) = 167772160 bits await input.fill('167772160'); @@ -50,6 +50,7 @@ test.describe('Bit Flipper large input rendering', () => { test('very large bitcount (500M bits) renders MB groups ~60', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('500000000'); // loader check removed, optimized MB rendering should be fast enough @@ -63,6 +64,7 @@ test.describe('Bit Flipper large input rendering', () => { test('inputting 2kb worth of bits shows correct KB groups and bytes, then updating input should update canvas count', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); // 2 KB = 2 * 8192 = 16384 bits await input.fill('16384'); @@ -113,6 +115,7 @@ test.describe('Bit Flipper large input rendering', () => { test('repeatedly updating input with KB values updates KB canvas count without regression', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); const kbs = page.locator('.kb-block'); @@ -169,16 +172,19 @@ test.describe('Bit Flipper large input rendering', () => { const unitSelect = page.locator('#unit-select'); const mode = page.locator('#mode'); - // By default binary mode should be selected and unit select hidden - await expect(mode).toHaveValue('binary'); + // By default bitcount mode should be selected and unit select visible + await expect(mode).toHaveValue('bitcount'); + await expect(unitSelect).toBeVisible(); + await expect(inputLabel).toHaveText('Enter MB count'); + + // Switch to binary and ensure unitSelect hidden and label updates + await page.selectOption('#mode', 'binary'); await expect(unitSelect).toBeHidden(); - await expect(inputLabel).toHaveText('Enter bit count:'); + await expect(inputLabel).toHaveText('Enter integer'); - // Switch to bitcount and ensure unitSelect visible and label updates + // Switch back to bitcount await page.selectOption('#mode', 'bitcount'); await expect(unitSelect).toBeVisible(); - // default unit 'bits' should use bit label - await expect(inputLabel).toHaveText('Enter bit count:'); // change unit to KB and check label await page.selectOption('#unit-select', 'KB'); @@ -187,6 +193,7 @@ test.describe('Bit Flipper large input rendering', () => { test('repeatedly updating input with MB values updates MB canvas count without regression', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); const mbs = page.locator('.mb-block'); // deterministic pseudo-random sequence so the test is repeatable @@ -209,6 +216,7 @@ test.describe('Bit Flipper large input rendering', () => { test('repeatedly updating input with GB values updates GB canvas count without regression', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); const gbs = page.locator('.gb-block'); // deterministic pseudo-random sequence so the test is repeatable diff --git a/tests/playwright/test-number-inputs.spec.ts b/tests/playwright/test-number-inputs.spec.ts index bd2cfbe..780f448 100644 --- a/tests/playwright/test-number-inputs.spec.ts +++ b/tests/playwright/test-number-inputs.spec.ts @@ -26,6 +26,7 @@ test.describe('Bit Flipper number input tests', () => { test('inputting 1 bit shows 1 partial byte', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('1'); const bytes = page.locator('.byte'); @@ -37,6 +38,7 @@ test.describe('Bit Flipper number input tests', () => { test('inputting 7 bits shows 1 partial byte', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('7'); const bytes = page.locator('.byte'); @@ -48,6 +50,7 @@ test.describe('Bit Flipper number input tests', () => { test('inputting non-multiple of 8 bits shows correct bytes and partial byte', async ({ page }) => { await page.goto('/'); await page.selectOption('#mode', 'bitcount'); + await page.selectOption('#unit-select', 'bits'); const input = page.locator('#number-input'); await input.fill('1030'); const bytes = page.locator('.byte'); From 0a365d26b9d3bcc3bb5e1854bfaaf91390a1751a Mon Sep 17 00:00:00 2001 From: Perezented Date: Wed, 8 Apr 2026 15:46:22 -0500 Subject: [PATCH 4/4] Fix label initialization on page load to reflect default mode+unit --- static/app.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/static/app.js b/static/app.js index 24500cb..c22304a 100644 --- a/static/app.js +++ b/static/app.js @@ -39,6 +39,17 @@ if (DEBUG_RENDER) console.log('ui-init-unit', { mode, hidden: hide, parentClass: unitSelect.parentElement.className }); } + // Initialize the input label to reflect the starting mode and unit + if (inputLabel) { + if (mode === 'bitcount') { + if (unit === 'bits') inputLabel.textContent = 'Enter bit count:'; + else if (unit === 'bytes') inputLabel.textContent = 'Enter byte count'; + else inputLabel.textContent = `Enter ${unit} count`; + } else { + inputLabel.textContent = 'Enter integer'; + } + } + // Keep last bits to animate differences let lastBits = [];