Skip to content

feat(mobile): replace SAP landing screen with Android notes app#1

Open
devxvoid wants to merge 2 commits intomainfrom
codex/build-notes-app-for-android
Open

feat(mobile): replace SAP landing screen with Android notes app#1
devxvoid wants to merge 2 commits intomainfrom
codex/build-notes-app-for-android

Conversation

@devxvoid
Copy link
Copy Markdown
Owner

@devxvoid devxvoid commented Apr 28, 2026

Motivation

  • Provide a focused Android-ready notes screen in the mobile app as requested by replacing the SAP WebView landing/demo surface.
  • Prefer a simple, local-first example that demonstrates UI, persistence, and basic list interactions for mobile development and testing.
  • Remove reliance on the complex SAP RFUI injection flow from the default root screen so the app can be used independently.

Description

  • Replaced the SAP-focused root screen implementation with a notes app at artifacts/mobile/app/index.tsx that implements a header, multiline input, add button, note cards, delete action, and empty state.
  • Added local persistence using AsyncStorage under the key notes-app-v1 to load notes on startup and save on changes, including defensive handling of invalid stored payloads.
  • Implemented note metadata with generated IDs and createdAt timestamps, a note counter label derived from notes.length, and a styled FlatList rendering.
  • Removed the WebView + SAP inspection/injection code and the SAP-specific UI/logic from the replaced file and simplified styles to match the new notes UI.

Testing

  • Ran type checking with pnpm --filter @workspace/mobile typecheck, which exited with errors; the reported TypeScript syntax errors are located in artifacts/mobile/app/warehouse.tsx and are pre-existing and unrelated to the notes screen change.
  • No additional automated tests were added or run for the new UI beyond the TypeScript typecheck.

Codex Task


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request replaces the complex SAP WMS mobile application with a simplified notes application that uses AsyncStorage for local persistence. The review feedback identifies a critical race condition in the persistence logic where the initial empty state could overwrite existing data before the loading process completes. Additionally, there is a recommendation to optimize date formatting within the list's render function to prevent performance issues and ensure consistent behavior across different mobile JavaScript engines.

Comment thread artifacts/mobile/app/index.tsx Outdated
Comment on lines +24 to +42
const [notes, setNotes] = useState<Note[]>([]);
const [inputValue, setInputValue] = useState("");

useEffect(() => {
if (!page) return;

if (hasBlockingError(page)) {
if (page.kind === "login") {
setStage("login");
} else if (page.kind === "rfui-logon" || page.kind === "rfui-error") {
setStage("warehouse");
}
if (page.messages.length) {
setErrorText(page.messages[0]);
}
return;
}

if (stage === "boot") {
if (page.kind === "login") {
setStage("login");
setInfoText("Sign in with your SAP account.");
} else if (page.kind === "rfui-logon") {
setStage("warehouse");
setInfoText("SAP session found. Complete warehouse setup.");
} else if (isRealConnectedRfui(page)) {
setStage("ready");
setInfoText("Connected to SAP.");
}
return;
}

if (stage === "authenticating") {
if (page.kind === "rfui-logon") {
setStage("warehouse");
setErrorText("");
setInfoText("SAP login successful. Complete warehouse setup.");
} else if (isRealConnectedRfui(page)) {
setStage("ready");
setErrorText("");
setInfoText("SAP login successful.");
} else if (page.kind === "login" && page.messages.length) {
setStage("login");
setErrorText(page.messages[0]);
void (async () => {
const raw = await AsyncStorage.getItem(STORAGE_KEY);
if (!raw) return;
try {
const parsed = JSON.parse(raw) as Note[];
setNotes(parsed);
} catch {
await AsyncStorage.removeItem(STORAGE_KEY);
}
return;
}
})();
}, []);

if (stage === "submittingWarehouse") {
if (isRealConnectedRfui(page)) {
setStage("ready");
setErrorText("");
setInfoText("Warehouse session connected successfully.");
} else if (
page.kind === "rfui-logon" ||
page.kind === "rfui-error" ||
page.messages.length
) {
setStage("warehouse");
if (page.messages.length) {
setErrorText(page.messages[0]);
}
}
}
}, [page, stage]);

const inject = (script: string) => {
webViewRef.current?.injectJavaScript(script);
};

const injectInspector = () => {
inject(INSPECT_PAGE_JS);
};

const navigateHome = () => {
setErrorText("");
setInfoText("Opening SAP...");
inject(`window.location.href = ${escapeForInjectedJs(SAP_RFUI_URL)}; true;`);
};

const handleNavigationChange = (navState: WebViewNavigation) => {
setCanGoBack(navState.canGoBack);
};

const handleMessage = (event: WebViewMessageEvent) => {
try {
const data = JSON.parse(event.nativeEvent.data);

if (data.type === "PAGE_STATE" && data.payload) {
const nextPage: SapPageSnapshot = {
url: data.payload.url ?? "",
host: data.payload.host ?? "",
title: data.payload.title ?? "SAP",
kind: data.payload.kind ?? "unknown",
messages: Array.isArray(data.payload.messages) ? data.payload.messages : [],
hasWarehouse: !!data.payload.hasWarehouse,
hasResource: !!data.payload.hasResource,
hasDevice: !!data.payload.hasDevice,
warehouseValue: data.payload.warehouseValue ?? "",
resourceValue: data.payload.resourceValue ?? "",
deviceValue: data.payload.deviceValue ?? "",
isErrorPage: !!data.payload.isErrorPage,
};

setPage(nextPage);

if (nextPage.messages.length && stage !== "ready") {
setErrorText(nextPage.messages[0]);
}
return;
}

if (data.type === "ACTION_RESULT") {
if (!data.ok && data.error) {
setErrorText(String(data.error));
if (data.action === "login") {
setStage("login");
} else if (data.action === "warehouse") {
setStage("warehouse");
}
}
}
} catch {}
};

const submitLogin = () => {
const trimmedUser = username.trim();
const trimmedPass = password;

if (!trimmedUser || !trimmedPass) {
setErrorText("Enter your SAP email/username and password.");
return;
}

if (page?.kind === "rfui-logon") {
setStage("warehouse");
setErrorText("");
return;
}

if (isRealConnectedRfui(page)) {
setStage("ready");
setErrorText("");
return;
}

setErrorText("");
setInfoText("Signing in to SAP...");
setStage("authenticating");
inject(createLoginInjection(trimmedUser, trimmedPass, keepSignedIn));
};
useEffect(() => {
void AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
}, [notes]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

There is a race condition in the persistence logic. The useEffect responsible for saving notes (lines 40-42) runs on the initial render because notes is initialized to an empty array. Since AsyncStorage operations are asynchronous, this effect might overwrite existing stored notes with an empty array before the loading effect (lines 27-38) completes. I recommend adding an initialization flag to ensure that saving only occurs after the initial data has been loaded.

  const [notes, setNotes] = useState<Note[]>([]);
  const [inputValue, setInputValue] = useState("");
  const [isInitialized, setIsInitialized] = useState(false);

  useEffect(() => {
    void (async () => {
      try {
        const raw = await AsyncStorage.getItem(STORAGE_KEY);
        if (raw) {
          const parsed = JSON.parse(raw) as Note[];
          setNotes(parsed);
        }
      } catch {
        await AsyncStorage.removeItem(STORAGE_KEY);
      } finally {
        setIsInitialized(true);
      }
    })();
  }, []);

  useEffect(() => {
    if (isInitialized) {
      void AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(notes));
    }
  }, [notes, isInitialized]);

Comment thread artifacts/mobile/app/index.tsx Outdated
<Text style={styles.noteText}>{item.text}</Text>
<View style={styles.noteFooter}>
<Text style={styles.noteDate}>
{new Date(item.createdAt).toLocaleString()}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Calling new Date().toLocaleString() inside renderItem can lead to performance issues as the list grows and may produce inconsistent results across different JavaScript engines (e.g., JSC vs Hermes on Android). It is better to format the date once when the note is created or use a memoized component for the list items.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant