Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 52 additions & 11 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,10 @@
Alert,
Stack,
Flex,
HStack,
Text,

Check warning on line 14 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

'Text' is defined but never used
} from '@chakra-ui/react';
import type { DeviceModel } from '@/esp/useEspOperations';

Check warning on line 16 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

'DeviceModel' is defined but never used
Comment on lines +13 to +16
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused imports to fix pipeline failure.

Static analysis and the CI pipeline flag Text and DeviceModel as unused imports. Remove them to pass the lint check.

🔧 Proposed fix
   Stack,
   Flex,
-  HStack,
-  Text,
+  HStack,
 } from '@chakra-ui/react';
-import type { DeviceModel } from '@/esp/useEspOperations';
🧰 Tools
🪛 ESLint

[error] 15-15: Unable to resolve path to module '@chakra-ui/react'.

(import-x/no-unresolved)

🪛 GitHub Actions: CI

[warning] 14-14: ESLint (@typescript-eslint/no-unused-vars): 'Text' is defined but never used.


[warning] 16-16: ESLint (@typescript-eslint/no-unused-vars): 'DeviceModel' is defined but never used.

🪛 GitHub Check: lint

[warning] 16-16:
'DeviceModel' is defined but never used


[warning] 14-14:
'Text' is defined but never used

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/page.tsx` around lines 13 - 16, Remove the unused imports flagged by
CI: delete Text from the named import from '@chakra-ui/react' and remove the
unused type import DeviceModel from '@/esp/useEspOperations'; update any
remaining import formatting in the same module (e.g., keep HStack) and ensure
there are no remaining references to Text or DeviceModel in page.tsx so lint
passes.

import FileUpload, { FileUploadHandle } from '@/components/FileUpload';
import Steps from '@/components/Steps';
import { useEspOperations } from '@/esp/useEspOperations';
Expand All @@ -20,7 +23,8 @@
} from '@/remote/firmwareFetcher';

export default function Home() {
const { actions, stepData, isRunning } = useEspOperations();
const { actions, stepData, isRunning, deviceModel, setDeviceModel } =
useEspOperations();
const [officialFirmwareVersions, setOfficialFirmwareVersions] = useState<{
en: string;
ch: string;
Expand All @@ -32,15 +36,42 @@
const appPartitionFileInput = useRef<FileUploadHandle>(null);

useEffect(() => {
getOfficialFirmwareVersions().then((versions) =>
setOfficialFirmwareVersions(versions),
);
let cancelled = false;
setOfficialFirmwareVersions(null);
getOfficialFirmwareVersions(deviceModel).then((versions) => {
if (!cancelled) {
setOfficialFirmwareVersions(versions);
}
});

return () => {
cancelled = true;
};
}, [deviceModel]);

useEffect(() => {
getCommunityFirmwareRemoteData().then(setCommunityFirmwareVersions);
}, []);

return (
<Flex direction="column" gap="20px">
<Stack gap={3} as="section">
<Heading size="xl">Device model</Heading>
<HStack gap={3}>
{(['x4', 'x3'] as const).map((model) => (
<Button
key={model}
variant={deviceModel === model ? 'solid' : 'outline'}
aria-pressed={deviceModel === model}
onClick={() => setDeviceModel(model)}
disabled={isRunning}
>
Xteink {model.toUpperCase()}
</Button>
))}
</HStack>
</Stack>
Comment on lines +58 to +73
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don't expose the CrossPoint flash path in X3 mode yet.

This selector makes X3 look fully supported, but the community firmware path is still model-agnostic in src/remote/firmwareFetcher.ts. Users can now switch to X3 and still trigger a CrossPoint flash with no model scoping. Please disable that button for X3 until community firmware resolution is model-aware.

🧭 Minimal UI guard
           <Button
             variant="subtle"
             onClick={actions.flashCrossPointFirmware}
-            disabled={isRunning || !communityFirmwareVersions}
+            disabled={
+              isRunning || !communityFirmwareVersions || deviceModel === 'x3'
+            }
             loading={!communityFirmwareVersions}
           >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/page.tsx` around lines 58 - 73, The X3 selector currently allows
switching to 'x3' which can trigger model-agnostic CrossPoint flashing; update
the Device model buttons in the component that renders deviceModel (the map over
(['x4','x3'] as const) that calls setDeviceModel) to disable the X3 option until
community firmware resolution is model-aware: modify the Button disabled prop to
include model === 'x3' (e.g., disabled={isRunning || model === 'x3'}) and also
ensure aria-pressed/aria-disabled reflect the disabled state so X3 cannot be
selected while firmwareFetcher (src/remote/firmwareFetcher.ts) lacks model
scoping.

<Separator />
<Alert.Root status="warning">
<Alert.Indicator />
<Alert.Content>
Expand Down Expand Up @@ -208,8 +239,8 @@
<Alert.Title>Change device language</Alert.Title>
<Alert.Description>
Before starting the process, it is recommended to change the device
language to English. To do this, select Settings icon, then click
OK / Confirm button and OK / Confirm again until English is
language to English. To do this, select &ldquo;Settings&rdquo; icon, then click

Check failure on line 242 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

Delete `·then·click`
&ldquo;OK / Confirm&rdquo; button and &ldquo;OK / Confirm&rdquo; again until English is

Check failure on line 243 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

Replace `·&ldquo;OK·/·Confirm&rdquo;·button·and·&ldquo;OK·/·Confirm&rdquo;·again·until·English·is⏎············shown.·Otherwise,·the·language·will·still·be·Chinese·after·flashing⏎···········` with `·then·click·&ldquo;OK·/·Confirm&rdquo;·button·and·&ldquo;OK·/⏎············Confirm&rdquo;·again·until·English·is·shown.·Otherwise,·the·language⏎············will·still·be·Chinese·after·flashing`
shown. Otherwise, the language will still be Chinese after flashing
and you may not notice changes.
</Alert.Description>
Expand All @@ -220,13 +251,23 @@
<Alert.Content>
<Alert.Title>Device restart instructions</Alert.Title>
<Alert.Description>
Once you complete a write operation, you will need to restart your
device by pressing and releasing the small “Reset” button near the
bottom right, followed quickly by pressing and holding of the main
power button for about 3 seconds.
<Stack>
<p>
Once you complete a write operation, you will need to restart
your device by pressing and releasing the small &ldquo;Reset&rdquo; button

Check failure on line 257 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

Replace `·&ldquo;Reset&rdquo;·button⏎················near·the·bottom·right,·followed·quickly·by·pressing·and·holding⏎················of·the·main·power·button·for` with `⏎················&ldquo;Reset&rdquo;·button·near·the·bottom·right,·followed⏎················quickly·by·pressing·and·holding·of·the·main·power·button·for⏎···············`
near the bottom right, followed quickly by pressing and holding
of the main power button for about 3 seconds.
</p>
{deviceModel === 'x3' && (
<p>
For CrossPoint firmware, disconnect the USB cable and connect
it again instead.
</p>
)}
</Stack>
</Alert.Description>
</Alert.Content>
</Alert.Root>
</Flex>
);
}
}

Check failure on line 273 in src/app/page.tsx

View workflow job for this annotation

GitHub Actions / lint

Insert `⏎`
57 changes: 47 additions & 10 deletions src/esp/EspController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@
},
};

export interface PartitionLayout {
app0Offset: number;
app1Offset: number;
appSize: number;
}

export const X4_PARTITION_LAYOUT: PartitionLayout = {
app0Offset: 0x10000,
app1Offset: 0x650000,
appSize: 0x640000,
};

export const X3_PARTITION_LAYOUT: PartitionLayout = {
app0Offset: 0x10000,
app1Offset: 0x780000,
appSize: 0x770000,
};

export default class EspController {
static async requestDevice() {
if (!('serial' in navigator && navigator.serial)) {
Expand All @@ -54,15 +72,19 @@
});
}

static async fromRequestedDevice() {
static async fromRequestedDevice(
partitionLayout: PartitionLayout = X4_PARTITION_LAYOUT,
) {
const device = await this.requestDevice();
return new EspController(device);
return new EspController(device, partitionLayout);
}

private espLoader;
private layout: PartitionLayout;

Check failure on line 83 in src/esp/EspController.ts

View workflow job for this annotation

GitHub Actions / lint

Expected blank line between class members

constructor(device: SerialPort) {
constructor(device: SerialPort, partitionLayout: PartitionLayout = X4_PARTITION_LAYOUT) {

Check failure on line 85 in src/esp/EspController.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `device:·SerialPort,·partitionLayout:·PartitionLayout·=·X4_PARTITION_LAYOUT` with `⏎····device:·SerialPort,⏎····partitionLayout:·PartitionLayout·=·X4_PARTITION_LAYOUT,⏎··`
const transport = new Transport(device, false);
this.layout = partitionLayout;
this.espLoader = new ESPLoader({
transport,
baudrate: 115200,
Expand All @@ -71,12 +93,16 @@
});
}

setPartitionLayout(layout: PartitionLayout) {
this.layout = layout;
}

async connect() {
await this.espLoader.main();
}

async disconnect({ skipReset = false }: { skipReset?: boolean } = {}) {
await this.espLoader.after(skipReset ? 'no_reset' : 'hard_reset');
await this.espLoader.after(skipReset ? 'no_reset_stub' : 'hard_reset');
await this.espLoader.transport.disconnect();
}

Expand Down Expand Up @@ -180,8 +206,11 @@
totalSize: number,
) => void,
) {
const offset = partitionLabel === 'app0' ? 0x10000 : 0x650000;
return this.espLoader.readFlash(offset, 0x640000, onPacketReceived);
const offset =
partitionLabel === 'app0'
? this.layout.app0Offset
: this.layout.app1Offset;
return this.espLoader.readFlash(offset, this.layout.appSize, onPacketReceived);

Check failure on line 213 in src/esp/EspController.ts

View workflow job for this annotation

GitHub Actions / lint

Replace `offset,·this.layout.appSize,·onPacketReceived` with `⏎······offset,⏎······this.layout.appSize,⏎······onPacketReceived,⏎····`
}

async readAppPartitionForIdentification(
Expand All @@ -206,7 +235,10 @@
// In testing, most firmwares are identified within the first 25KB read, so reading the entire
// partition is unnecessary in the majority of cases.

const baseOffset = partitionLabel === 'app0' ? 0x10000 : 0x650000;
const baseOffset =
partitionLabel === 'app0'
? this.layout.app0Offset
: this.layout.app1Offset;

return this.espLoader.readFlash(
baseOffset + offset,
Expand All @@ -224,16 +256,21 @@
total: number,
) => void,
) {
if (data.length > 0x640000) {
throw new Error(`Data cannot be larger than 0x640000`);
if (data.length > this.layout.appSize) {
throw new Error(
`Data cannot be larger than 0x${this.layout.appSize.toString(16)}`,
);
}
if (data.length < 0xf0000) {
throw new Error(
`Data seems too small, are you sure this is the right file?`,
);
}

const offset = partitionLabel === 'app0' ? 0x10000 : 0x650000;
const offset =
partitionLabel === 'app0'
? this.layout.app0Offset
: this.layout.app1Offset;

await this.writeData(data, offset, reportProgress);
}
Expand Down
Loading