prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Go to newly created stream (with first-time modal)
  Even after #29154, users find it hard to navigate to a newly created stream. To address this, we should:
  
  1. Take the user directly to the stream they just created. To avoid a potentially confusing interleaved view, we should go to the most recent topic in the stream (currently "stream events", but might be "general chat" in the future).
  2. The first time that a user creates a stream, show an explanatory modal (wording to be finalized when working on the PR):
  
  ----
  
  ## Stream **#{stream name}** created!
  
  You will now see the stream you created. If you'd like to go back to stream settings, click on the name of the stream at the top of your Zulip window, or use the **back** button in your browser or desktop app.
  
  [Continue]
  
  ---
  
  Since we are changing the behavior, it's fine to show this once to existing users.
  
  [CZO thread](https://chat.zulip.org/#narrow/stream/137-feedback/topic/user.20research.3A.20going.20to.20a.20new.20stream/near/1744305)
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `zerver/lib/hotspots.py`
  Content:
  ```
  1 # See https://zulip.readthedocs.io/en/latest/subsystems/hotspots.html
  2 # for documentation on this subsystem.
  3 from dataclasses import dataclass
  4 from typing import Any, Dict, List, Optional, Union
  5 
  6 from django.conf import settings
  7 from django.utils.translation import gettext_lazy
  8 from django_stubs_ext import StrPromise
  9 
  10 from zerver.models import OnboardingStep, UserProfile
  11 
  12 
  13 @dataclass
  14 class Hotspot:
  15     name: str
  16     title: Optional[StrPromise]
  17     description: Optional[StrPromise]
  18     has_trigger: bool = False
  19 
  20     def to_dict(self, delay: float = 0) -> Dict[str, Union[str, float, bool]]:
  21         return {
  22             "type": "hotspot",
  23             "name": self.name,
  24             "title": str(self.title),
  25             "description": str(self.description),
  26             "delay": delay,
  27             "has_trigger": self.has_trigger,
  28         }
  29 
  30 
  31 INTRO_HOTSPOTS: List[Hotspot] = [
  32     Hotspot(
  33         name="intro_streams",
  34         title=gettext_lazy("Catch up on a stream"),
  35         description=gettext_lazy(
  36             "Messages sent to a stream are seen by everyone subscribed "
  37             "to that stream. Try clicking on one of the stream links below."
  38         ),
  39     ),
  40     Hotspot(
  41         name="intro_topics",
  42         title=gettext_lazy("Topics"),
  43         description=gettext_lazy(
  44             "Every message has a topic. Topics keep conversations "
  45             "easy to follow, and make it easy to reply to conversations that start "
  46             "while you are offline."
  47         ),
  48     ),
  49     Hotspot(
  50         # In theory, this should be renamed to intro_personal, since
  51         # it's no longer attached to the gear menu, but renaming these
  52         # requires a migration that is not worth doing at this time.
  53         name="intro_gear",
  54         title=gettext_lazy("Settings"),
  55         description=gettext_lazy("Go to Settings to configure your notifications and preferences."),
  56     ),
  57     Hotspot(
  58         name="intro_compose",
  59         title=gettext_lazy("Compose"),
  60         description=gettext_lazy(
  61             "Click here to start a new conversation. Pick a topic "
  62             "(2-3 words is best), and give it a go!"
  63         ),
  64     ),
  65 ]
  66 
  67 
  68 NON_INTRO_HOTSPOTS: List[Hotspot] = []
  69 
  70 
  71 @dataclass
  72 class OneTimeNotice:
  73     name: str
  74 
  75     def to_dict(self) -> Dict[str, str]:
  76         return {
  77             "type": "one_time_notice",
  78             "name": self.name,
  79         }
  80 
  81 
  82 ONE_TIME_NOTICES: List[OneTimeNotice] = [
  83     OneTimeNotice(
  84         name="visibility_policy_banner",
  85     ),
  86     OneTimeNotice(
  87         name="intro_inbox_view_modal",
  88     ),
  89     OneTimeNotice(
  90         name="intro_recent_view_modal",
  91     ),
  92 ]
  93 
  94 # We would most likely implement new hotspots in the future that aren't
  95 # a part of the initial tutorial. To that end, classifying them into
  96 # categories which are aggregated in ALL_HOTSPOTS, seems like a good start.
  97 ALL_HOTSPOTS = [*INTRO_HOTSPOTS, *NON_INTRO_HOTSPOTS]
  98 ALL_ONBOARDING_STEPS: List[Union[Hotspot, OneTimeNotice]] = [*ALL_HOTSPOTS, *ONE_TIME_NOTICES]
  99 
  100 
  101 def get_next_onboarding_steps(user: UserProfile) -> List[Dict[str, Any]]:
  102     # For manual testing, it can be convenient to set
  103     # ALWAYS_SEND_ALL_HOTSPOTS=True in `zproject/dev_settings.py` to
  104     # make it easy to click on all of the hotspots.
  105     #
  106     # Since this is just for development purposes, it's convenient for us to send
  107     # all the hotspots rather than any specific category.
  108     if settings.ALWAYS_SEND_ALL_HOTSPOTS:
  109         return [hotspot.to_dict() for hotspot in ALL_HOTSPOTS]
  110 
  111     # If a Zulip server has disabled the tutorial, never send hotspots.
  112     if not settings.TUTORIAL_ENABLED:
  113         return []
  114 
  115     seen_onboarding_steps = frozenset(
  116         OnboardingStep.objects.filter(user=user).values_list("onboarding_step", flat=True)
  117     )
  118 
  119     onboarding_steps: List[Dict[str, Any]] = [hotspot.to_dict() for hotspot in NON_INTRO_HOTSPOTS]
  120 
  121     for one_time_notice in ONE_TIME_NOTICES:
  122         if one_time_notice.name in seen_onboarding_steps:
  123             continue
  124         onboarding_steps.append(one_time_notice.to_dict())
  125 
  126     if user.tutorial_status == UserProfile.TUTORIAL_FINISHED:
  127         return onboarding_steps
  128 
  129     for hotspot in INTRO_HOTSPOTS:
  130         if hotspot.name in seen_onboarding_steps:
  131             continue
  132 
  133         onboarding_steps.append(hotspot.to_dict(delay=0.5))
  134         return onboarding_steps
  135 
  136     user.tutorial_status = UserProfile.TUTORIAL_FINISHED
  137     user.save(update_fields=["tutorial_status"])
  138     return onboarding_steps
  139 
  140 
  141 def copy_hotspots(source_profile: UserProfile, target_profile: UserProfile) -> None:
  142     for userhotspot in frozenset(OnboardingStep.objects.filter(user=source_profile)):
  143         OnboardingStep.objects.create(
  144             user=target_profile,
  145             onboarding_step=userhotspot.onboarding_step,
  146             timestamp=userhotspot.timestamp,
  147         )
  148 
  149     target_profile.tutorial_status = source_profile.tutorial_status
  150     target_profile.onboarding_steps = source_profile.onboarding_steps
  151     target_profile.save(update_fields=["tutorial_status", "onboarding_steps"])
  152
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/zerver/lib/hotspots.py b/zerver/lib/hotspots.py
  --- a/zerver/lib/hotspots.py
  +++ b/zerver/lib/hotspots.py
  @@ -89,6 +89,9 @@
       OneTimeNotice(
           name="intro_recent_view_modal",
       ),
  +    OneTimeNotice(
  +        name="first_stream_created_banner",
  +    ),
   ]
   
   # We would most likely implement new hotspots in the future that aren't
  
verification_info: {"golden_diff": "diff --git a/zerver/lib/hotspots.py b/zerver/lib/hotspots.py\n--- a/zerver/lib/hotspots.py\n+++ b/zerver/lib/hotspots.py\n@@ -89,6 +89,9 @@\n     OneTimeNotice(\n         name=\"intro_recent_view_modal\",\n     ),\n+    OneTimeNotice(\n+        name=\"first_stream_created_banner\",\n+    ),\n ]\n \n # We would most likely implement new hotspots in the future that aren't\n", "issue": "Go to newly created stream (with first-time modal)\nEven after #29154, users find it hard to navigate to a newly created stream. To address this, we should:\r\n\r\n1. Take the user directly to the stream they just created. To avoid a potentially confusing interleaved view, we should go to the most recent topic in the stream (currently \"stream events\", but might be \"general chat\" in the future).\r\n2. The first time that a user creates a stream, show an explanatory modal (wording to be finalized when working on the PR):\r\n\r\n----\r\n\r\n## Stream **#{stream name}** created!\r\n\r\nYou will now see the stream you created. If you'd like to go back to stream settings, click on the name of the stream at the top of your Zulip window, or use the **back** button in your browser or desktop app.\r\n\r\n[Continue]\r\n\r\n---\r\n\r\nSince we are changing the behavior, it's fine to show this once to existing users.\r\n\r\n[CZO thread](https://chat.zulip.org/#narrow/stream/137-feedback/topic/user.20research.3A.20going.20to.20a.20new.20stream/near/1744305)\n", "before_files": [{"content": "# See https://zulip.readthedocs.io/en/latest/subsystems/hotspots.html\n# for documentation on this subsystem.\nfrom dataclasses import dataclass\nfrom typing import Any, Dict, List, Optional, Union\n\nfrom django.conf import settings\nfrom django.utils.translation import gettext_lazy\nfrom django_stubs_ext import StrPromise\n\nfrom zerver.models import OnboardingStep, UserProfile\n\n\n@dataclass\nclass Hotspot:\n    name: str\n    title: Optional[StrPromise]\n    description: Optional[StrPromise]\n    has_trigger: bool = False\n\n    def to_dict(self, delay: float = 0) -> Dict[str, Union[str, float, bool]]:\n        return {\n            \"type\": \"hotspot\",\n            \"name\": self.name,\n            \"title\": str(self.title),\n            \"description\": str(self.description),\n            \"delay\": delay,\n            \"has_trigger\": self.has_trigger,\n        }\n\n\nINTRO_HOTSPOTS: List[Hotspot] = [\n    Hotspot(\n        name=\"intro_streams\",\n        title=gettext_lazy(\"Catch up on a stream\"),\n        description=gettext_lazy(\n            \"Messages sent to a stream are seen by everyone subscribed \"\n            \"to that stream. Try clicking on one of the stream links below.\"\n        ),\n    ),\n    Hotspot(\n        name=\"intro_topics\",\n        title=gettext_lazy(\"Topics\"),\n        description=gettext_lazy(\n            \"Every message has a topic. Topics keep conversations \"\n            \"easy to follow, and make it easy to reply to conversations that start \"\n            \"while you are offline.\"\n        ),\n    ),\n    Hotspot(\n        # In theory, this should be renamed to intro_personal, since\n        # it's no longer attached to the gear menu, but renaming these\n        # requires a migration that is not worth doing at this time.\n        name=\"intro_gear\",\n        title=gettext_lazy(\"Settings\"),\n        description=gettext_lazy(\"Go to Settings to configure your notifications and preferences.\"),\n    ),\n    Hotspot(\n        name=\"intro_compose\",\n        title=gettext_lazy(\"Compose\"),\n        description=gettext_lazy(\n            \"Click here to start a new conversation. Pick a topic \"\n            \"(2-3 words is best), and give it a go!\"\n        ),\n    ),\n]\n\n\nNON_INTRO_HOTSPOTS: List[Hotspot] = []\n\n\n@dataclass\nclass OneTimeNotice:\n    name: str\n\n    def to_dict(self) -> Dict[str, str]:\n        return {\n            \"type\": \"one_time_notice\",\n            \"name\": self.name,\n        }\n\n\nONE_TIME_NOTICES: List[OneTimeNotice] = [\n    OneTimeNotice(\n        name=\"visibility_policy_banner\",\n    ),\n    OneTimeNotice(\n        name=\"intro_inbox_view_modal\",\n    ),\n    OneTimeNotice(\n        name=\"intro_recent_view_modal\",\n    ),\n]\n\n# We would most likely implement new hotspots in the future that aren't\n# a part of the initial tutorial. To that end, classifying them into\n# categories which are aggregated in ALL_HOTSPOTS, seems like a good start.\nALL_HOTSPOTS = [*INTRO_HOTSPOTS, *NON_INTRO_HOTSPOTS]\nALL_ONBOARDING_STEPS: List[Union[Hotspot, OneTimeNotice]] = [*ALL_HOTSPOTS, *ONE_TIME_NOTICES]\n\n\ndef get_next_onboarding_steps(user: UserProfile) -> List[Dict[str, Any]]:\n    # For manual testing, it can be convenient to set\n    # ALWAYS_SEND_ALL_HOTSPOTS=True in `zproject/dev_settings.py` to\n    # make it easy to click on all of the hotspots.\n    #\n    # Since this is just for development purposes, it's convenient for us to send\n    # all the hotspots rather than any specific category.\n    if settings.ALWAYS_SEND_ALL_HOTSPOTS:\n        return [hotspot.to_dict() for hotspot in ALL_HOTSPOTS]\n\n    # If a Zulip server has disabled the tutorial, never send hotspots.\n    if not settings.TUTORIAL_ENABLED:\n        return []\n\n    seen_onboarding_steps = frozenset(\n        OnboardingStep.objects.filter(user=user).values_list(\"onboarding_step\", flat=True)\n    )\n\n    onboarding_steps: List[Dict[str, Any]] = [hotspot.to_dict() for hotspot in NON_INTRO_HOTSPOTS]\n\n    for one_time_notice in ONE_TIME_NOTICES:\n        if one_time_notice.name in seen_onboarding_steps:\n            continue\n        onboarding_steps.append(one_time_notice.to_dict())\n\n    if user.tutorial_status == UserProfile.TUTORIAL_FINISHED:\n        return onboarding_steps\n\n    for hotspot in INTRO_HOTSPOTS:\n        if hotspot.name in seen_onboarding_steps:\n            continue\n\n        onboarding_steps.append(hotspot.to_dict(delay=0.5))\n        return onboarding_steps\n\n    user.tutorial_status = UserProfile.TUTORIAL_FINISHED\n    user.save(update_fields=[\"tutorial_status\"])\n    return onboarding_steps\n\n\ndef copy_hotspots(source_profile: UserProfile, target_profile: UserProfile) -> None:\n    for userhotspot in frozenset(OnboardingStep.objects.filter(user=source_profile)):\n        OnboardingStep.objects.create(\n            user=target_profile,\n            onboarding_step=userhotspot.onboarding_step,\n            timestamp=userhotspot.timestamp,\n        )\n\n    target_profile.tutorial_status = source_profile.tutorial_status\n    target_profile.onboarding_steps = source_profile.onboarding_steps\n    target_profile.save(update_fields=[\"tutorial_status\", \"onboarding_steps\"])\n", "path": "zerver/lib/hotspots.py"}], "after_files": [{"content": "# See https://zulip.readthedocs.io/en/latest/subsystems/hotspots.html\n# for documentation on this subsystem.\nfrom dataclasses import dataclass\nfrom typing import Any, Dict, List, Optional, Union\n\nfrom django.conf import settings\nfrom django.utils.translation import gettext_lazy\nfrom django_stubs_ext import StrPromise\n\nfrom zerver.models import OnboardingStep, UserProfile\n\n\n@dataclass\nclass Hotspot:\n    name: str\n    title: Optional[StrPromise]\n    description: Optional[StrPromise]\n    has_trigger: bool = False\n\n    def to_dict(self, delay: float = 0) -> Dict[str, Union[str, float, bool]]:\n        return {\n            \"type\": \"hotspot\",\n            \"name\": self.name,\n            \"title\": str(self.title),\n            \"description\": str(self.description),\n            \"delay\": delay,\n            \"has_trigger\": self.has_trigger,\n        }\n\n\nINTRO_HOTSPOTS: List[Hotspot] = [\n    Hotspot(\n        name=\"intro_streams\",\n        title=gettext_lazy(\"Catch up on a stream\"),\n        description=gettext_lazy(\n            \"Messages sent to a stream are seen by everyone subscribed \"\n            \"to that stream. Try clicking on one of the stream links below.\"\n        ),\n    ),\n    Hotspot(\n        name=\"intro_topics\",\n        title=gettext_lazy(\"Topics\"),\n        description=gettext_lazy(\n            \"Every message has a topic. Topics keep conversations \"\n            \"easy to follow, and make it easy to reply to conversations that start \"\n            \"while you are offline.\"\n        ),\n    ),\n    Hotspot(\n        # In theory, this should be renamed to intro_personal, since\n        # it's no longer attached to the gear menu, but renaming these\n        # requires a migration that is not worth doing at this time.\n        name=\"intro_gear\",\n        title=gettext_lazy(\"Settings\"),\n        description=gettext_lazy(\"Go to Settings to configure your notifications and preferences.\"),\n    ),\n    Hotspot(\n        name=\"intro_compose\",\n        title=gettext_lazy(\"Compose\"),\n        description=gettext_lazy(\n            \"Click here to start a new conversation. Pick a topic \"\n            \"(2-3 words is best), and give it a go!\"\n        ),\n    ),\n]\n\n\nNON_INTRO_HOTSPOTS: List[Hotspot] = []\n\n\n@dataclass\nclass OneTimeNotice:\n    name: str\n\n    def to_dict(self) -> Dict[str, str]:\n        return {\n            \"type\": \"one_time_notice\",\n            \"name\": self.name,\n        }\n\n\nONE_TIME_NOTICES: List[OneTimeNotice] = [\n    OneTimeNotice(\n        name=\"visibility_policy_banner\",\n    ),\n    OneTimeNotice(\n        name=\"intro_inbox_view_modal\",\n    ),\n    OneTimeNotice(\n        name=\"intro_recent_view_modal\",\n    ),\n    OneTimeNotice(\n        name=\"first_stream_created_banner\",\n    ),\n]\n\n# We would most likely implement new hotspots in the future that aren't\n# a part of the initial tutorial. To that end, classifying them into\n# categories which are aggregated in ALL_HOTSPOTS, seems like a good start.\nALL_HOTSPOTS = [*INTRO_HOTSPOTS, *NON_INTRO_HOTSPOTS]\nALL_ONBOARDING_STEPS: List[Union[Hotspot, OneTimeNotice]] = [*ALL_HOTSPOTS, *ONE_TIME_NOTICES]\n\n\ndef get_next_onboarding_steps(user: UserProfile) -> List[Dict[str, Any]]:\n    # For manual testing, it can be convenient to set\n    # ALWAYS_SEND_ALL_HOTSPOTS=True in `zproject/dev_settings.py` to\n    # make it easy to click on all of the hotspots.\n    #\n    # Since this is just for development purposes, it's convenient for us to send\n    # all the hotspots rather than any specific category.\n    if settings.ALWAYS_SEND_ALL_HOTSPOTS:\n        return [hotspot.to_dict() for hotspot in ALL_HOTSPOTS]\n\n    # If a Zulip server has disabled the tutorial, never send hotspots.\n    if not settings.TUTORIAL_ENABLED:\n        return []\n\n    seen_onboarding_steps = frozenset(\n        OnboardingStep.objects.filter(user=user).values_list(\"onboarding_step\", flat=True)\n    )\n\n    onboarding_steps: List[Dict[str, Any]] = [hotspot.to_dict() for hotspot in NON_INTRO_HOTSPOTS]\n\n    for one_time_notice in ONE_TIME_NOTICES:\n        if one_time_notice.name in seen_onboarding_steps:\n            continue\n        onboarding_steps.append(one_time_notice.to_dict())\n\n    if user.tutorial_status == UserProfile.TUTORIAL_FINISHED:\n        return onboarding_steps\n\n    for hotspot in INTRO_HOTSPOTS:\n        if hotspot.name in seen_onboarding_steps:\n            continue\n\n        onboarding_steps.append(hotspot.to_dict(delay=0.5))\n        return onboarding_steps\n\n    user.tutorial_status = UserProfile.TUTORIAL_FINISHED\n    user.save(update_fields=[\"tutorial_status\"])\n    return onboarding_steps\n\n\ndef copy_hotspots(source_profile: UserProfile, target_profile: UserProfile) -> None:\n    for userhotspot in frozenset(OnboardingStep.objects.filter(user=source_profile)):\n        OnboardingStep.objects.create(\n            user=target_profile,\n            onboarding_step=userhotspot.onboarding_step,\n            timestamp=userhotspot.timestamp,\n        )\n\n    target_profile.tutorial_status = source_profile.tutorial_status\n    target_profile.onboarding_steps = source_profile.onboarding_steps\n    target_profile.save(update_fields=[\"tutorial_status\", \"onboarding_steps\"])\n", "path": "zerver/lib/hotspots.py"}]}