Skip to content

Conversation

yihao03
Copy link
Member

@yihao03 yihao03 commented May 14, 2025

Description

updated router to read language params from url and request for the corresponding page in the backend
added button in sicp page to switch between languages

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update
  • Code quality improvements

How to test

use the updated backend that supports i18n when starting the frontend
navigate to the sicp page in the frontend and switch between languages

Checklist

  • I have tested this code
  • I have updated the documentation

Summary by CodeRabbit

  • New Features

    • Added bilingual support (English and Chinese) for the SICP textbook, including a language toggle button.
    • Language preference is now saved and restored using local storage.
    • URLs can now include a language prefix to directly access content in a specific language.
  • Enhancements

    • Table of contents and content are dynamically loaded based on the selected language, with improved error handling and loading indicators.
    • Added route support for language-prefixed URLs under the SICP path.

@coder114514 coder114514 marked this pull request as ready for review June 10, 2025 14:37
@coder114514 coder114514 requested a review from RichDom2185 June 17, 2025 06:15
@RichDom2185 RichDom2185 requested a review from Copilot July 5, 2025 08:53
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR introduces internationalization support for the SICP pages by reading language parameters from the URL and local storage, fetching language-specific content, and adding a toggle UI to switch languages.

  • Added a language segment to the SICP route configuration
  • Refactored the table of contents component to fetch TOC JSON per language with loading and fallback
  • Enhanced the main SICP page to initialize, persist, and toggle language, and to redirect based on the language parameter

Reviewed Changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.

File Description
src/routes/routerConfig.tsx Added :param_lang to the SICP route and updated route definitions
src/pages/sicp/subcomponents/SicpToc.tsx Refactored TOC into a loading/fallback pattern with language fetch
src/pages/sicp/Sicp.tsx Handled URL/local-storage language, added toggle button, and redirects
src/features/sicp/utils/SicpUtils.ts Introduced language storage helpers and default-language constants

@RichDom2185
Copy link
Member

Please take whatever Copilot says with a big mountain of salt, I will review it properly later…

@RichDom2185 RichDom2185 enabled auto-merge (squash) August 9, 2025 09:00
Copy link

coderabbitai bot commented Aug 10, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The changes introduce bilingual support (English and Chinese) for the SICP textbook interface. Language preference is now managed via local storage, URL parameters, and a UI toggle. The table of contents and content fetching logic are updated to dynamically load language-specific resources. Routing is enhanced to recognize language-prefixed URLs.

Changes

Cohort / File(s) Change Summary
SICP Language Preference Utilities
src/features/sicp/utils/SicpUtils.ts
Adds constants and functions for managing SICP language preference in local storage, including setting, reading, and dispatching a language change event.
SICP Main Page Language Support
src/pages/sicp/Sicp.tsx
Integrates language selection state, initialization from local storage or URL, language toggle UI, content fetching by language, and updates effects and navigation to support bilingual content.
SICP Table of Contents Refactor & i18n
src/pages/sicp/subcomponents/SicpToc.tsx
Refactors TOC rendering into a child component, adds language-aware dynamic TOC fetching with loading and error handling, and listens for language change events.
Routing for Language Parameter
src/routes/routerConfig.tsx
Adds a new route pattern to support URLs with a language parameter before the section, enabling navigation to language-specific SICP content.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant SicpPage
    participant LocalStorage
    participant Router
    participant TOCComponent
    participant Backend

    User->>SicpPage: Visit /sicpjs or /sicpjs/:param_lang/:section?
    SicpPage->>LocalStorage: Read language preference
    SicpPage->>Router: Parse URL for :param_lang
    alt Language in URL
        SicpPage->>LocalStorage: Set language from URL
        SicpPage->>Router: Navigate to URL without :param_lang
    end
    SicpPage->>TOCComponent: Render with selected language
    TOCComponent->>Backend: Fetch TOC JSON for language
    alt Fetch fails
        TOCComponent->>TOCComponent: Load static fallback TOC
    end
    User->>SicpPage: Click language toggle button
    SicpPage->>LocalStorage: Update language
    SicpPage->>TOCComponent: Trigger re-render with new language
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Poem

In fields of code where languages grow,
A toggle appears—now SICP’s bilingual, you know!
English and Chinese, side by side,
With local storage where settings reside.
URLs now speak in many a tongue—
Rabbits rejoice, their work is well done!
🐇✨

Note

🔌 MCP (Model Context Protocol) integration is now available in Early Access!

Pro users can now connect to remote MCP servers under the Integrations page to get reviews and chat conversations that understand additional development context.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

‼️ IMPORTANT
Auto-reply has been disabled for this repository in the CodeRabbit settings. The CodeRabbit bot will not respond to your replies unless it is explicitly tagged.

  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@RichDom2185
Copy link
Member

@coderabbitai full review

Copy link

coderabbitai bot commented Aug 10, 2025

✅ Actions performed

Full review triggered.

@coveralls
Copy link

Pull Request Test Coverage Report for Build 16858394883

Details

  • 62 of 115 (53.91%) changed or added relevant lines in 4 files are covered.
  • 6 unchanged lines in 1 file lost coverage.
  • Overall coverage increased (+0.003%) to 43.288%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/features/sicp/utils/SicpUtils.ts 7 10 70.0%
src/pages/sicp/subcomponents/SicpToc.tsx 30 53 56.6%
src/pages/sicp/Sicp.tsx 24 51 47.06%
Files with Coverage Reduction New Missed Lines %
src/pages/sicp/subcomponents/SicpToc.tsx 6 51.16%
Totals Coverage Status
Change from base Build 16858234296: 0.003%
Covered Lines: 20574
Relevant Lines: 49714

💛 - Coveralls

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/routes/routerConfig.tsx (1)

52-54: Align param name and route specificity; place the 2‑segment route first and use camelCase :paramLang.

Current route uses :param_lang while Sicp.tsx reads paramLang, causing the param to be undefined. Also, put the language-prefixed route before the generic one to avoid ambiguous matches.

Apply this diff:

   { path: 'contributors', lazy: Contributors },
   { path: 'callback/github', lazy: GitHubCallback },
-  { path: 'sicpjs/:section?', lazy: Sicp },
-  { path: 'sicpjs/:param_lang/:section?', lazy: Sicp },
+  { path: 'sicpjs/:paramLang/:section?', lazy: Sicp },
+  { path: 'sicpjs/:section?', lazy: Sicp },
   { path: 'features', lazy: Features }
src/pages/sicp/subcomponents/SicpToc.tsx (1)

24-26: Flatten Toc props instead of passing a nested props object.

This avoids an extra layer and improves readability.

-const Toc: React.FC<{ toc: TreeNodeInfo[]; props: TocProps }> = ({ toc, props }) => {
+const Toc: React.FC<TocProps & { toc: TreeNodeInfo[] }> = ({ toc, handleCloseToc }) => {
...
-  const handleNodeClicked = React.useCallback(
+  const handleNodeClicked = React.useCallback(
     (node: TreeNodeInfo) => {
-      if (props.handleCloseToc) {
-        props.handleCloseToc();
-      }
+      if (handleCloseToc) handleCloseToc();
       navigate('/sicpjs/' + String(node.nodeData));
     },
-    [navigate, props]
+    [navigate, handleCloseToc]
   );
...
-        <Toc toc={fallbackToc as TreeNodeInfo[]} props={props} />
+        <Toc toc={fallbackToc as TreeNodeInfo[]} {...props} />
       ) : (
-        <Toc toc={toc} props={props} />
+        <Toc toc={toc} {...props} />
       )}

Also applies to: 101-104

src/pages/sicp/Sicp.tsx (1)

57-59: Param mismatch with router; destructure snake_case or rename the route param.

If you keep the route as :param_lang, destructure it as below to avoid undefined. If you adopt the router rename to :paramLang (recommended), this change isn’t needed.

-  const { paramLang, section } = useParams<{ paramLang: string; section: string }>();
+  const { param_lang: paramLang, section } =
+    useParams<{ param_lang?: string; section?: string }>();
🧹 Nitpick comments (8)
src/features/sicp/utils/SicpUtils.ts (2)

18-21: Prefer a named constant for the event and dispatch a CustomEvent with the language payload.

Improves resilience (one source of truth for the event name) and lets listeners react without re-reading LS.

+export const SICP_LANG_CHANGE_EVENT = 'sicp-tb-lang-change' as const;
+
 export const setSicpLangLocalStorage = (value: string) => {
   setLocalStorage(SICP_TB_LANG_KEY, value);
-  window.dispatchEvent(new Event('sicp-tb-lang-change'));
+  window.dispatchEvent(new CustomEvent(SICP_LANG_CHANGE_EVENT, { detail: { lang: value } }));
 };

23-26: Optionally expose a narrower type for the language value.

If you standardize the available languages, returning a union helps avoid accidental values across the app.

-export const readSicpLangLocalStorage = () => {
-  const data = readLocalStorage(SICP_TB_LANG_KEY, SICP_DEF_TB_LANG);
-  return data;
-};
+export type SicpLang = 'en' | 'zh_CN';
+export const readSicpLangLocalStorage = (): SicpLang => {
+  return readLocalStorage(SICP_TB_LANG_KEY, SICP_DEF_TB_LANG) as SicpLang;
+};
src/pages/sicp/subcomponents/SicpToc.tsx (1)

88-89: Use console.error for error logging.

Minor logging nit for error visibility.

-        console.log(error);
+        console.error(error);
src/pages/sicp/Sicp.tsx (5)

41-41: Type the available languages as a literal union using as const.

This improves type safety across the module and pairs well with a SicpLang type alias.

-const AVAILABLE_SICP_TB_LANGS: readonly string[] = ['en', 'zh_CN'];
+const AVAILABLE_SICP_TB_LANGS = ['en', 'zh_CN'] as const;
+type SicpLang = (typeof AVAILABLE_SICP_TB_LANGS)[number];

173-174: Use the dedicated helper for section storage for consistency.

Use setSicpSectionLocalStorage instead of raw setLocalStorage.

-        setLocalStorage(SICP_CACHE_KEY, SICP_INDEX); // Prevents caching invalid page
+        setSicpSectionLocalStorage(SICP_INDEX); // Prevents caching invalid page

214-227: Nit: Raise zIndex of the sticky language toggle to avoid overlap issues.

At zIndex: 0, the button can end up beneath content in some stacking contexts.

-    <div
-      style={{
-        position: 'sticky',
-        top: '20px',
-        left: '20px',
-        zIndex: 0
-      }}
-    >
+    <div
+      style={{
+        position: 'sticky',
+        top: '20px',
+        left: '20px',
+        zIndex: 10
+      }}
+    >

139-147: Guarding invalid lang inside the fetch effect is fine, but consider constraining the state type.

With a SicpLang union, this guard becomes unnecessary.


16-23: Ensure docs and tests cover the breaking i18n changes.

This is a breaking change per PR summary; add:

  • Routing examples with/without the language segment
  • E2E/interaction tests for language toggling and persistence
  • Notes on required backend (sicpBackendUrl/json/{lang}/...) for local dev

I can draft a short doc section and a couple of tests if helpful.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66024de and 17c2745.

⛔ Files ignored due to path filters (2)
  • src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap is excluded by !**/*.snap
  • src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • src/features/sicp/utils/SicpUtils.ts (1 hunks)
  • src/pages/sicp/Sicp.tsx (7 hunks)
  • src/pages/sicp/subcomponents/SicpToc.tsx (3 hunks)
  • src/routes/routerConfig.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/pages/sicp/subcomponents/SicpToc.tsx (1)
src/features/sicp/utils/SicpUtils.ts (1)
  • readSicpLangLocalStorage (23-26)
src/features/sicp/utils/SicpUtils.ts (1)
src/commons/utils/LocalStorageHelper.ts (2)
  • setLocalStorage (6-8)
  • readLocalStorage (1-4)
src/pages/sicp/Sicp.tsx (1)
src/features/sicp/utils/SicpUtils.ts (4)
  • readSicpLangLocalStorage (23-26)
  • setSicpLangLocalStorage (18-21)
  • SICP_DEF_TB_LANG (15-15)
  • readSicpSectionLocalStorage (10-13)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: lint (test-coveralls)
  • GitHub Check: lint (test --bail=1)
🔇 Additional comments (2)
src/pages/sicp/Sicp.tsx (2)

107-123: Confirm intended URL canonicalization (removing the lang segment after reading).

Current logic reads the language from the URL, saves it, then navigates to a URL without the language segment. If the goal is shareable, language-stable URLs, consider preserving it:

  • With lang param present: navigate(/sicpjs/${paramLang}/${section ?? readSicpSectionLocalStorage()}, { replace: true })
  • Otherwise, keep current behavior.

Would you like to keep URLs canonical without the lang prefix, or preserve the language in the URL for shareability?


41-51: Good abstraction: loadInitialLang reduces first-render churn.

This addresses earlier feedback about separating LS/default logic from render and avoids an extra re-render on mount.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (4)
src/routes/routerConfig.tsx (1)

52-54: Route ordering and param name mismatch block language-aware route

Place the more specific two-segment route before the generic one, and align the param name with the component’s useParams (paramLang).

-  { path: 'sicpjs/:section?', lazy: Sicp },
-  { path: 'sicpjs/:param_lang/:section?', lazy: Sicp },
+  { path: 'sicpjs/:paramLang/:section?', lazy: Sicp },
+  { path: 'sicpjs/:section?', lazy: Sicp },
src/pages/sicp/subcomponents/SicpToc.tsx (2)

28-31: Fix invalid TypeScript type integer[]number[]

This won’t compile; use number[].

-  const handleNodeExpand = (_node: TreeNodeInfo, path: integer[]) => {
+  const handleNodeExpand = (_node: TreeNodeInfo, path: number[]) => {
 ...
-  const handleNodeCollapse = (_node: TreeNodeInfo, path: integer[]) => {
+  const handleNodeCollapse = (_node: TreeNodeInfo, path: number[]) => {

Also applies to: 34-38


75-94: Reset error state on retry/success to avoid permanent fallback

Once error is set, the fallback is always shown. Reset it before fetch or on success.

   React.useEffect(() => {
     setLoading(true);
+    setError(false);
     fetch(baseUrl + lang + '/toc.json')
       .then(response => {
         if (!response.ok) {
           throw Error(response.statusText);
         }
         return response.json();
       })
       .then(json => {
         setToc(json as TreeNodeInfo[]);
+        setError(false);
       })
       .catch(error => {
-        console.log(error);
+        console.error(error);
         setError(true);
       })
       .finally(() => {
         setLoading(false);
       });
   }, [lang]);
src/pages/sicp/Sicp.tsx (1)

57-59: Critical: route param name mismatch breaks language parsing

If the router keeps :param_lang, paramLang here will always be undefined, causing wrong language to load. Either rename the route param to :paramLang (recommended; see router comment) or destructure with alias:

-  const { paramLang, section } = useParams<{ paramLang?: SicpLang; section?: string }>();
+  const { param_lang: paramLang, section } = useParams<{ param_lang?: SicpLang; section?: string }>();

Apply only if the router still uses :param_lang.

Also applies to: 105-122

🧹 Nitpick comments (2)
src/features/sicp/utils/SicpUtils.ts (1)

15-26: Type-safety and event-name constant for resilience

Add explicit return/param types and centralize the event name to avoid typos and ease reuse.

 export const SICP_DEF_TB_LANG = 'en';
 export const SICP_TB_LANG_KEY = 'sicp-textbook-lang';

+export const SICP_TB_LANG_CHANGE_EVENT = 'sicp-tb-lang-change';
+
-export const setSicpLangLocalStorage = (value: string) => {
-  setLocalStorage(SICP_TB_LANG_KEY, value);
-  window.dispatchEvent(new Event('sicp-tb-lang-change'));
-};
+export const setSicpLangLocalStorage = (value: string): void => {
+  setLocalStorage(SICP_TB_LANG_KEY, value);
+  window.dispatchEvent(new Event(SICP_TB_LANG_CHANGE_EVENT));
+};
 
-export const readSicpLangLocalStorage = () => {
-  const data = readLocalStorage(SICP_TB_LANG_KEY, SICP_DEF_TB_LANG);
-  return data;
-};
+export const readSicpLangLocalStorage = (): string => {
+  const data = readLocalStorage(SICP_TB_LANG_KEY, SICP_DEF_TB_LANG);
+  return data as string;
+};
src/pages/sicp/Sicp.tsx (1)

204-208: Minor UX/accessibility polish for the language toggle

Add ARIA attributes and avoid hardcoding mixed-language labels.

-  const handleLanguageToggle = () => {
-    const newLang = lang === 'en' ? 'zh_CN' : 'en';
+  const handleLanguageToggle = () => {
+    const newLang: SicpLang = lang === 'en' ? 'zh_CN' : 'en';
     setLang(newLang);
     setSicpLangLocalStorage(newLang);
   };
 ...
-      <Button onClick={handleLanguageToggle} intent="primary" small>
-        {lang === 'en' ? '切换到中文' : 'Switch to English'}
+      <Button
+        onClick={handleLanguageToggle}
+        intent="primary"
+        small
+        aria-pressed={lang === 'zh_CN'}
+        aria-label={lang === 'en' ? 'Switch to Chinese' : 'Switch to English'}
+      >
+        {lang === 'en' ? 'Switch to 中文' : '切换到 English'}
       </Button>

Also applies to: 214-229

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 66024de and 17c2745.

⛔ Files ignored due to path filters (2)
  • src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpIndexPage.test.tsx.snap is excluded by !**/*.snap
  • src/pages/sicp/subcomponents/__tests__/__snapshots__/SicpToc.test.tsx.snap is excluded by !**/*.snap
📒 Files selected for processing (4)
  • src/features/sicp/utils/SicpUtils.ts (1 hunks)
  • src/pages/sicp/Sicp.tsx (7 hunks)
  • src/pages/sicp/subcomponents/SicpToc.tsx (3 hunks)
  • src/routes/routerConfig.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/pages/sicp/subcomponents/SicpToc.tsx (1)
src/features/sicp/utils/SicpUtils.ts (1)
  • readSicpLangLocalStorage (23-26)
src/features/sicp/utils/SicpUtils.ts (1)
src/commons/utils/LocalStorageHelper.ts (2)
  • setLocalStorage (6-8)
  • readLocalStorage (1-4)
src/pages/sicp/Sicp.tsx (1)
src/features/sicp/utils/SicpUtils.ts (4)
  • readSicpLangLocalStorage (23-26)
  • setSicpLangLocalStorage (18-21)
  • SICP_DEF_TB_LANG (15-15)
  • readSicpSectionLocalStorage (10-13)
🔇 Additional comments (2)
src/pages/sicp/Sicp.tsx (2)

105-122: Confirm intent: redirect strips the language segment

The redirect drops the /:paramLang prefix after reading it. If shareable URLs should preserve language, redirect to the two-segment path instead.

Option A (keep current behavior): no change.

Option B (preserve language in URL):

-      if (paramLang) {
-        navigate(`/sicpjs/${section}`, { replace: true });
-      } else {
-        navigate(`/sicpjs/${readSicpSectionLocalStorage()}`, { replace: true });
-      }
+      if (paramLang && section) {
+        navigate(`/sicpjs/${paramLang}/${section}`, { replace: true });
+      } else if (paramLang) {
+        navigate(`/sicpjs/${paramLang}/${readSicpSectionLocalStorage()}`, { replace: true });
+      } else {
+        navigate(`/sicpjs/${readSicpSectionLocalStorage()}`, { replace: true });
+      }

Please confirm which behavior is desired.


141-145: Good guard: ensure valid language before fetching

This prevents bad requests if lang is ever corrupted.

Consider centralizing this check in a small helper to DRY across components that fetch SICP content.

@RichDom2185 RichDom2185 marked this pull request as draft August 19, 2025 15:18
auto-merge was automatically disabled August 19, 2025 15:18

Pull request was converted to draft

@RichDom2185 RichDom2185 requested a review from sayomaki August 19, 2025 16:26
@RichDom2185
Copy link
Member

@sayomaki this should be ok now but can you review my code? Though it's still blocked by the SICP PR source-academy/sicp#1082

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants